Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't INotifyCollectionChanged extend IList?

I've ran into the situation a couple of times where I want to observe a collection through the INotifyCollectionChanged interface, but also want to be able to access any of the collection's elements. The INotifyCollectionChanged interface doesn't provide any way to access the elements, except for those that are involved in the change event (which (usually) are contained in the NotifyCollectionChangedEventArgs).

Now here's my thinking:

  1. We know that whatever implements INotifyCollectionChanged is a collection (d'uh).
  2. Since the NotifyPropertyChangedEventArgs contains indices indication the location of the change, we know that the elements can be accessed by index.

A collection that can be accessed by index is a list, so it seems to be that it would make sense to require that any INotifyCollectionChanged implementor also implements IList. This could easily be done by letting INotifyCollectionChanged extend IList.

Does anyone know why this is not the case?

like image 257
Steven Avatar asked Sep 30 '22 00:09

Steven


2 Answers

I think you need to look up the SOLID software design principles, specifically the Liskov Substitution Principle.

You asked why the INotifyCollectionChanged interface does not also extend the IList interface. Let me answer it with a counter question using the Liskov Subsitution Principle:

Can I say an INotifyCollectionChanged is an IList?

No I don't think so, for the following reasons:

  1. INotifyCollectionChanged conveys the meaning that classes implementing this interface need to notify their users if its underlying collection was changed, whether that underlying collection is an IList or ICollection, or even IEnumerable, we do not know. It's different concept of an IList interface, which is simply an ICollection with an exposed indexer

  2. You mentioned NotifyPropertyChangedEventArgs (which I believe you meant NotifyCollectionChangedEventArgs instead) exposes properties of the indices indicating at what position the collection is changed. However this does not mean these properties necessarily expose the items through the indexers of IList. It can be an arbitrary number, a magic constant, whatever. It is up to the implementing class to decide how to expose the indices.

To demonstrate this, please take a look at my custom class that implements INotifyCollectionChanged:

public class MyCustomCollection : INotifyCollectionChanged
{
    // This is what I meant by the "underlying collection", can be replaced with
    // ICollection<int> and it will still work, or even IEnumerable<int> but with some
    // code change to store the elements in an array
    private readonly IList<int> _ints;

    public MyCustomCollection()
    {
        _ints = new List<int>();
    }

    public event NotifyCollectionChangedEventHandler CollectionChanged;

    public void AddInt(int i)
    {
        _ints.Add(i);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(
            NotifyCollectionChangedAction.Move, 
            (IList)_ints, 
            _ints.Count,
            _ints.Count - 1));
    }

    protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        var handler = CollectionChanged;
        if (handler != null)
        {
            handler(this, e);
        }
    }
}

Hope this answer your question.

like image 122
rexcfnghk Avatar answered Nov 07 '22 05:11

rexcfnghk


I don’t know why, but I see reasons for that decision (without any knowledge of the decision – it’s just my opinion):

  1. Implementing INotifyCollectionChanged this way follows the principle of role-interfaces (the change notification is described rather than the object) Please see @rexcfnghk post for that!

  2. Only the changed elements in the EventArgs are represented as an IList<T>, that does not mean that the collection itself needs to be a list

To conclude, they made a decision to make the default collection with change notification a Collection<T> using an interface that allows that was required.

like image 26
MrWombat Avatar answered Nov 07 '22 05:11

MrWombat