Why is ReadOnlyObservableCollection.CollectionChanged not public?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
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

