Why is ReadOnlyObservableCollection.CollectionChanged not public?

后端 未结 8 2102
心在旅途
心在旅途 2021-01-31 06:50

Why is ReadOnlyObservableCollection.CollectionChanged protected and not public (as the corresponding ObservableCollection.CollectionChanged is)?

What is the use of a col

相关标签:
8条回答
  • 2021-01-31 07:19

    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.

    0 讨论(0)
  • 2021-01-31 07:23

    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.

    0 讨论(0)
  • 2021-01-31 07:24

    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;
    }
    
    0 讨论(0)
  • 2021-01-31 07:25

    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

    0 讨论(0)
  • 2021-01-31 07:31

    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.

    0 讨论(0)
  • 2021-01-31 07:32

    Solution

    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);
    }
    

    Explanation

    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 from ObservableCollection by the constructor of ReadOnlyObservableCollection.

    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.

    0 讨论(0)
提交回复
热议问题