FGF: A Collection Class with Events

For most purposes, the builtin collection classes supplied by the .NET Framework are terrific. They are simple and straight forward implementations that do exactly as you expect. But for our purposes, a collection class that fires events when the collection is changed is simply better and in some cases, necessary. For this class, we make the jump over to the FocusedGames.Collections namespace, maintained in the FocusedGames library.

Before writing the collection class itself, we need a delegate that can define how our events will work. Enter the CollectionChangedHandler delegate.

public delegate void CollectionChangedHandler(object sender, CollectionChangedEventArgs args);

As you can see, we need to define the CollectionChangedEventArgs class itself. This is a simple class that has two properties. The two properties help listeners of the events to understand how the collection was changed.

public class CollectionChangedEventArgs : EventArgs
{
    public CollectionChangedEventArgs()
    {
    }

    public CollectionChangedEventArgs(object item)
    {
        Item = item;
    }

    public CollectionChangedEventArgs(object item, int index)
        : this(item)
    {
        Index = index;
    }

    public object Item { get; set; }
    public int Index { get; set; }
}

In the future, I hope that I can implement the INotifyCollectionChanged interface from the .NET Framework. Unfortunately I refuse to do so because the interface is placed in a DLL that is not supported on some frameworks that I need to support with the FG Framework. Instead, I have written my own ICollection interface that basically takes the place of the .NET Framework’s interface.

public interface ICollection
{
    event CollectionChangedHandler ItemAdded;
    event CollectionChangedHandler ItemRemoved;
}

When combined with the .NET Framework’s List class, a powerful collection class is created.

public class Collection : ICollection, IList, ICollection, IEnumerable, IList, System.Collections.ICollection, IEnumerable
{
    public event CollectionChangedHandler ItemAdded;
    public event CollectionChangedHandler ItemRemoved;

    private List internalList = new List();

    private bool IsViableType(object item)
    {
        if(item is T && item != null) return true;
        return false;
    }

    protected virtual void OnItemAdded(object sender, CollectionChangedEventArgs args)
    {
        if (ItemAdded != null) ItemAdded.Invoke(sender, args);
    }

    protected virtual void OnItemRemoved(object sender, CollectionChangedEventArgs args)
    {
        if (ItemRemoved != null) ItemRemoved.Invoke(sender, args);
    }

But it is more complicated than that. We have to implement every single interface. I am not going to bother explaining each one because they are all simple pass through methods.

#region Sorting
    public void Sort()
    {
        internalList.Sort();
    }

    public void Sort(IComparer comparer)
    {
        internalList.Sort(comparer);
    }
    #endregion

    public void AddRange(IEnumerable items)
    {
        foreach (T item in items)
            Add(item);
    }

    #region IList Members
    public int IndexOf(T item)
    {
        return internalList.IndexOf(item);
    }

    public void Insert(int index, T item)
    {
        internalList.Insert(index, item);

        OnItemAdded(this, new CollectionChangedEventArgs(item, index));
    }

    public void RemoveAt(int index)
    {
        T item = this[index];

        internalList.RemoveAt(index);

        OnItemRemoved(this, new CollectionChangedEventArgs(item, index));
    }

    public T this[int index]
    {
        get
        {
            return internalList[index];
        }
        set
        {
            internalList[index] = value;
        }
    }
    #endregion

    #region ICollection Members
    public void Add(T item)
    {
        internalList.Add(item);

        OnItemAdded(this, new CollectionChangedEventArgs(item, Count - 1));
    }

    public void Clear()
    {
        // TODO: How do throw the event here?
        for (int i = Count - 1; i > 0; i--)
            RemoveAt(i);

        internalList.Clear();
    }

    public bool Contains(T item)
    {
        return internalList.Contains(item);
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        internalList.CopyTo(array, arrayIndex);
    }

    public int Count
    {
        get { return internalList.Count; }
    }

    bool ICollection.IsReadOnly
    {
        get { return false; }
    }

    public bool Remove(T item)
    {
        int index = internalList.IndexOf(item);

        bool returnValue = internalList.Remove(item);

        OnItemRemoved(this, new CollectionChangedEventArgs(item, index));

        return returnValue;
    }
    #endregion

    #region IEnumerable Members
    public IEnumerator GetEnumerator()
    {
        return internalList.GetEnumerator();
    }
    #endregion

    #region IEnumerable Members
    IEnumerator IEnumerable.GetEnumerator()
    {
        return internalList.GetEnumerator();
    }
    #endregion

    #region IList Members
    int IList.Add(object value)
    {
        if (IsViableType(value))
        {
            Add((T)value);

            return internalList.IndexOf((T)value);
        }

        return -1;
    }

    bool IList.Contains(object value)
    {
        if (IsViableType(value))
        {
            return Contains((T)value);
        }

        return false;
    }

    int IList.IndexOf(object value)
    {
        if(IsViableType(value))
            return internalList.IndexOf((T)value);

        return -1;
    }

    void IList.Insert(int index, object value)
    {
        if (IsViableType(value))
            Insert(index, (T)value);
    }

    bool IList.IsFixedSize
    {
        get { return false; }
    }

    void IList.Remove(object value)
    {
        if (IsViableType(value))
            Remove((T)value);
    }

    object IList.this[int index]
    {
        get
        {
            return internalList[index];
        }
        set
        {
            if (value is T && value != null)
                internalList[index] = (T)value;
        }
    }

    bool IList.IsReadOnly
    {
        get { return false; }
    }
    #endregion

    #region ICollection Members
    void System.Collections.ICollection.CopyTo(Array array, int index)
    {
        throw new NotImplementedException();
    }

    bool System.Collections.ICollection.IsSynchronized
    {
        get { return false; }
    }

    object System.Collections.ICollection.SyncRoot
    {
        get { return null; }
    }
    #endregion
}

And there you have it, a collection that fires events when an item is added or removed. In the future, I plan to clean this class up. I decided to include the non-generic interfaces only because the .NET Framework’s List class has implemented them.

2 thoughts on “FGF: A Collection Class with Events

  1. Having taken that route already, allow me to make some suggestions ;-)

    Is there any reason why you avoid generics in that code? I would hate to have to downcast in the event handler. Especially since with this design, if I later changed the collection’s type, those downcasts would still happily compile and only fail a runtime.

    You also don’t seem to derive from System.Collections.ObjectModel.Collection (which is made for this purpose and would implement IList, ICollection, IEnumerable, IList, ICollection and IEnumerable for you)

    Supplying an index *and* the object seems redundant. Such a talkative interface limits the concept to indexable collections. I have had a use for observable dictionaries in the past :)

    Here’s my hammer for the same nail: :P
    https://devel.nuclex.org/framework/browser/Nuclex.Support/trunk/Source/Collections/ItemEventArgs.cs
    https://devel.nuclex.org/framework/browser/Nuclex.Support/trunk/Source/Collections/ObservableCollection.cs
    https://devel.nuclex.org/framework/browser/Nuclex.Support/trunk/Source/Collections/ObservableDictionary.cs

  2. I am assuming that you are speaking about the event portion of the code when you talk about avoiding generics? The reason for this is that a single event handler couldn’t listen to multiple lists. It is a very small and insignificant reason, I know, but it also allows for complete coverage of use cases.

    As for deriving from System…Collection – I honestly didn’t know it existed. I will have to double check to see if this object is available on the 360 and Zune HD because Reflector seems to be having issues reflecting on them.

    The reason for the index and the object is because originally this interface was modeled after the INotifyCollectionChanged interface, which supplies both the index and the object as well as another parameter telling how the collection was changed. I am hoping that with the release of .NET 4.0, this interface will be better placed in the framework’s libraries such that I can use it. This will give me interoperability with other .NET software.

    Again, the fact that the index exists does not limit the use to index based collections but does supply complete coverage which is an overriding goal for FGF. The point here is that if it is needed or can be used, the index parameter is there. If it weren’t there than the use would be limited to non-indexable collections.

    Having said all that, your implementation seems perfectly reasonable and is a good way of approaching the situation.

Leave a Reply

Your email address will not be published. Required fields are marked *

*


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>