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:
INotifyCollectionChanged
is a collection (d'uh).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?
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 anIList
?
No I don't think so, for the following reasons:
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
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.
I don’t know why, but I see reasons for that decision (without any knowledge of the decision – it’s just my opinion):
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!
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With