Observing changes to a List<T> by adding events

In an attempt to get more C# and .NET content up I’m putting up some snippets I’ve put together in response to questions on some C# user support groups. Many of them are not particularly advanced but they are quite useful.

GitHub has the latest version of ObservableList

This sample shows how to observe events on an generic IList. It does this by way of implementing the IList interface over the top of something that already supports IList to do the actual work and highlights how useful publishing the interface, IList, separate from the actual concrete class List can be for reuse.

using System;
using System.Collections;
using System.Collections.Generic;

public class ObservableList<T> : IList<T> {
  private IList<T> internalList;

  public class ListChangedEventArgs : EventArgs {
    public int index;
    public T item;
    public ListChangedEventArgs(int index, T item) {
      this.index = index;
      this.item = item;
    }
  }

  public delegate void ListChangedEventHandler(object source, ListChangedEventArgs e);
  public delegate void ListClearedEventHandler(object source, EventArgs e);
  public event ListChangedEventHandler ListChanged;
  public event ListClearedEventHandler ListCleared;

  public ObservableList() {
    internalList = new List<T>();
  }

  public ObservableList(IList<T> list) {
    internalList = list;
  }

  public ObservableList(IEnumerable<T> collection) {
    internalList = new List<T>(collection);
  }

  protected virtual void OnListChanged(ListChangedEventArgs e) {
    if (ListChanged != null)
      ListChanged(this, e);
  }

  protected virtual void OnListCleared(EventArgs e) {
    if (ListCleared != null)
      ListCleared(this, e);
  }

  public int IndexOf(T item) {
    return internalList.IndexOf(item);
  }

  public void Insert(int index, T item) {
    internalList.Insert(index, item);
    OnListChanged(new ListChangedEventArgs(index, item));
  }

  public void RemoveAt(int index) {
    T item = internalList[index];
    internalList.Remove(item);
    OnListChanged(new ListChangedEventArgs(index, item));
  }

  public T this[int index] {
    get { return internalList[index]; }
    set {
          internalList[index] = value;
          OnListChanged(new ListChangedEventArgs(index, value));
    }
  }

  public void Add(T item) {
    internalList.Add(item);
    OnListChanged(new ListChangedEventArgs(internalList.IndexOf(item), item));
  }

  public void Clear() {
    internalList.Clear();
    OnListCleared(new EventArgs());
  }

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

  public bool IsReadOnly {
    get { return internalList.IsReadOnly; }
  }

  public bool Remove(T item) {
    lock(this) {
      int index = internalList.IndexOf(item);
      if (internalList.Remove(item)) {
        OnListChanged(new ListChangedEventArgs(index, item));
        return true;
      }
      else
        return false;
    }
  }

  public IEnumerator<T> GetEnumerator() {
    return internalList.GetEnumerator();
  }

  IEnumerator IEnumerable.GetEnumerator() {
    return ((IEnumerable) internalList).GetEnumerator();
  }
}

[)amien

8 responses

  1. Avatar for Will Gant

    How does this compare to using BindingList? It already had list changed events built in.

    Will Gant 14 June 2006
  2. Avatar for Steve

    I smell a Decorator pattern :)

    Steve 15 June 2006
  3. Avatar for Damien Guard

    BindingList does indeed have events but it's primary purpose is for data binding and the events and sorting are exposed as part of that.

    I was going to put that it was an illustration of the decorator pattern but couldn't find a decent decorator reference online to link to and didn't feel like trying to cover that myself.

    Damien Guard 15 June 2006
  4. Avatar for Steve Harman

    Damien, This is good stuff, as always!

    Oh, and I even found you a link to a pretty decent high-level explaination of the Decorator Pattern. Gotta' love Wikipedia!

    Steve Harman 15 June 2006
  5. Avatar for Jean-Paul S. Boodhoo

    Hey Damien, In case you are looking for a good example of the Decorator pattern, you can check out the article I wrote.

    Jean-Paul S. Boodhoo 16 June 2006
  6. Avatar for Damien Guard

    Good stuff guys.

    Damien Guard 2 August 2007
  7. Avatar for Mo Samara

    Nice one Damien, your CopyTo implementation should be internalList.CopyTo though, other wise you will get the nice StackOverflow exception.

    Mo Samara 19 February 2011
  8. Avatar for Sailor

    Great post. Thank you for this. One note that took me several hours to realize. The RemoveAt(int index) method is broken. Passing any index, it will remove the item starting at the beginning index of 0. I remember there being a bug in .Net 2.0 regarding this and I think they fixed in later releases but since Unity only supports 2.0, felt it was worthwhile to mention. If you find your list removing the wrong items when you call RemoveAt(), try replacing the RemoveAt method with the following:

    public void RemoveAt(int index) {
        T item = internalList[index];
        internalList.RemoveAt(index);
        OnListChanged(new ListChangedEventArgs(index, item));
    }
    

    Thanks again. Works great!

    Sailor 11 July 2014