Why is ReadOnlyObservableCollection.CollectionChanged protected and not public (as the corresponding ObservableCollection.CollectionChanged is)?
What is the use of a col
I've found a way for you of how to do this:
ObservableCollection<string> obsCollection = new ObservableCollection<string>();
INotifyCollectionChanged collection = new ReadOnlyObservableCollection<string>(obsCollection);
collection.CollectionChanged += new NotifyCollectionChangedEventHandler(collection_CollectionChanged);
You just need to refer to your collection explicitly by INotifyCollectionChanged interface.
There are definitely good reasons for wanting to subscribe to collection changed notifications on a ReadOnlyObservableCollection. So, as an alternative to merely casting your collection as INotifyCollectionChanged, if you happen to be subclassing ReadOnlyObservableCollection, then the following provides a more syntactically convenient way to access the a CollectionChanged event:
public class ReadOnlyObservableCollectionWithCollectionChangeNotifications<T> : ReadOnlyObservableCollection<T>
{
public ReadOnlyObservableCollectionWithCollectionChangeNotifications(ObservableCollection<T> list)
: base(list)
{
}
event System.Collections.Specialized.NotifyCollectionChangedEventHandler CollectionChanged2
{
add { CollectionChanged += value; }
remove { CollectionChanged -= value; }
}
}
This has worked well for me before.
This was top hit on google so I figured I'd add my solution in case other people look this up.
Using the information above (about needing to cast to INotifyCollectionChanged), I made two extension methods to register and unregister.
My Solution - Extension Methods
public static void RegisterCollectionChanged(this INotifyCollectionChanged collection, NotifyCollectionChangedEventHandler handler)
{
collection.CollectionChanged += handler;
}
public static void UnregisterCollectionChanged(this INotifyCollectionChanged collection, NotifyCollectionChangedEventHandler handler)
{
collection.CollectionChanged -= handler;
}
Example
IThing.cs
public interface IThing
{
string Name { get; }
ReadOnlyObservableCollection<int> Values { get; }
}
Using the Extension Methods
public void AddThing(IThing thing)
{
//...
thing.Values.RegisterCollectionChanged(this.HandleThingCollectionChanged);
}
public void RemoveThing(IThing thing)
{
//...
thing.Values.UnregisterCollectionChanged(this.HandleThingCollectionChanged);
}
OP's Solution
public void AddThing(IThing thing)
{
//...
INotifyCollectionChanged thingCollection = thing.Values;
thingCollection.CollectionChanged += this.HandleThingCollectionChanged;
}
public void RemoveThing(IThing thing)
{
//...
INotifyCollectionChanged thingCollection = thing.Values;
thingCollection.CollectionChanged -= this.HandleThingCollectionChanged;
}
Alternative 2
public void AddThing(IThing thing)
{
//...
(thing.Values as INotifyCollectionChanged).CollectionChanged += this.HandleThingCollectionChanged;
}
public void RemoveThing(IThing thing)
{
//...
(thing.Values as INotifyCollectionChanged).CollectionChanged -= this.HandleThingCollectionChanged;
}
As answered already, you have two options: you can either cast the ReadOnlyObservableCollection<T>
to the interface INotifyCollectionChanged
to access the explicitly implemented CollectionChanged
event, or you can create your own wrapper class that does that once in the constructor and just hooks up the events of the wrapped ReadOnlyObservableCollection<T>
.
Some additional insights into why this issue has not been fixed yet:
As you can see from the source code, ReadOnlyObservableCollection<T>
is a public, non-sealed (i. e. inheritable) class, where the events are marked protected virtual
.
That is, there might be compiled programs with classes that are derived from ReadOnlyObservableCollection<T>
, with overridden event definitions but protected
visibility. Those programs would contain invalid code once the event's visiblity is changed to public
in the base class, because it is not allowed to restrict the visibility of an event in derived classes.
So unfortunately, making protected virtual
events public
later on is a binary-breaking change, and hence it will not be done without very good reasoning, which I am afraid "I have to cast the object once to attach handlers" simply isn't.
Source: GitHub comment by Nick Guererra, August 19th, 2015
I know this post is old, however, people should take their time to understand the patterns used in .NET before commenting. A read only collection is a wrapper on an existing collection that prevents consumers from modifying it directly, look at ReadOnlyCollection
and you will see that it is a wrapper on a IList<T>
which may or may not be mutable. Immutable collections are a different matter and are covered by the new immutable collections library
In other words, read only is not the same as immutable!!!!
That aside, ReadOnlyObservableCollection
should implicitly implement INotifyCollectionChanged
.
ReadOnlyObservableCollection.CollectionChanged
is not exposed (for valid reasons outlined in other answers), so let's make our own wrapper class that exposes it:
/// <summary>A wrapped <see cref="ReadOnlyObservableCollection{T}"/> that exposes the internal <see cref="CollectionChanged"/>"/>.</summary>
public class ObservableReadOnlyCollection<T> : ReadOnlyObservableCollection<T>
{
public new NotifyCollectionChangedEventHandler CollectionChanged;
public ObservableReadOnlyCollection(ObservableCollection<T> list) : base(list) { /* nada */ }
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs args) =>
CollectionChanged?.Invoke(this, args);
}
People have asked why you would want to observe changes to a read-only collection, so I'll explain one of many valid situations; when the read-only collection wraps a private internal collection that can change.
Here's one such scenario:
Suppose you have a service that allows adding and removing items to an internal collection from outside the service. Now suppose you want to expose the values of the collection but you don't want consumers to manipulate the collection directly; so you wrap the internal collection in a ReadOnlyObservableCollection
.
Note that in order to wrap the internal collection with
ReadOnlyObservableCollection
the internal collection is forced to derive fromObservableCollection
by the constructor ofReadOnlyObservableCollection
.
Now suppose you want to notify consumers of the service when the internal collection changes (and hence when the exposed ReadOnlyObservableCollection
changes). Rather than rolling your own implementation you just want to expose the CollectionChanged
of the ReadOnlyObservableCollection
. Rather than forcing the consumer to make an assumption about the implementation of the ReadOnlyObservableCollection
, you simply swap the ReadOnlyObservableCollection
with this custom ObservableReadOnlyCollection
, and you're done.
The ObservableReadOnlyCollection
hides ReadOnlyObservableCollection.CollectionChanged
with it's own, and simply passes on all the collection changed events to any attached event handler.