Why is ReadOnlyObservableCollection.CollectionChanged not public?
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
ReadOnlyObservableCollection<T>.CollectionChanged is not public because it implements the INotifyCollectionChanged interface explicitly. This means the event is accessible only through the interface, not through the concrete type. The design enforces the read-only contract — consumers cast to INotifyCollectionChanged to subscribe, while the collection prevents direct manipulation of the event from the outside. This is an intentional .NET design pattern, not a bug.
The Problem
The Fix: Cast to INotifyCollectionChanged
Why Explicit Interface Implementation?
Explicit interface implementation means the member is only visible when accessed through the interface type, not the concrete class:
Design Rationale
The .NET team chose explicit implementation for several reasons:
- Read-only contract clarity: Making
CollectionChangedhidden reinforces that this collection should not be modified or subscribed to casually. It signals that change notification is an advanced scenario. - Preventing accidental subscriptions: A public event encourages casual subscriptions that may not be unsubscribed, causing memory leaks. The cast requirement makes subscriptions deliberate.
- Interface segregation: The class exposes
IList<T>andIReadOnlyList<T>functionality publicly. Change notification is a separate concern accessed throughINotifyCollectionChanged.
WPF Data Binding Works Automatically
WPF's data binding engine internally casts to INotifyCollectionChanged, so binding works without any special code:
The ListBox updates automatically when items are added to _items because WPF detects INotifyCollectionChanged on the bound collection.
Creating a Wrapper with Public Event
If the explicit interface is inconvenient, create a wrapper:
Extension Method Approach
Other Explicitly Implemented Members
ReadOnlyObservableCollection<T> also explicitly implements INotifyPropertyChanged:
Common Pitfalls
- Thinking CollectionChanged does not exist: The event exists — it is just hidden behind an explicit interface implementation. Cast to
INotifyCollectionChangedto access it. This is a deliberate design choice, not a missing feature. - Memory leaks from unsubscribed handlers: Event subscriptions prevent garbage collection of the subscriber. When using the cast pattern, store the handler in a variable so you can unsubscribe later:
var handler = ...; ((INotifyCollectionChanged)readOnly).CollectionChanged -= handler;. - Modifying the ReadOnlyObservableCollection directly: There is no way to add or remove items from the read-only collection. Changes must go through the underlying
ObservableCollection<T>that was passed to the constructor. - Losing the source reference: The
ReadOnlyObservableCollection<T>holds a reference to the sourceObservableCollection<T>, but it does not expose it publicly. Keep a reference to the source if you need to modify the collection later. - Assuming the wrapper prevents all mutations:
ReadOnlyObservableCollection<T>prevents direct add/remove, but if the items themselves are mutable objects, their properties can still be changed. For true immutability, use immutable item types.
Summary
CollectionChangedis explicitly implemented viaINotifyCollectionChanged, not missing- Cast to
INotifyCollectionChangedto subscribe:((INotifyCollectionChanged)collection).CollectionChanged += handler - WPF data binding works automatically — it internally checks for the interface
- Create a subclass or use extension methods to expose the event publicly if needed
- Changes propagate from the underlying
ObservableCollection<T>to the read-only wrapper automatically
Related reading
- Why is System.Version in .NET defined as Major.Minor.Build.Revision?
- Why is System.Web.Mvc not listed in Add References?
- Why is TaskAwaiter not awaitable?
- Why is TaskScheduler.Current the default TaskScheduler?
- Why is TaskT faster than ValueTaskT?
- Why is the task is not cancelled when I call CancellationTokenSource's Cancel method in async method?
- Why is the use of reflection in .NET recommended?
- Why is the Visual Studio 2015/2017/2019 Test Runner not discovering my xUnit v2 tests

OOD Fundamentals
Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.