Skip to content
Craig Fowler edited this page Dec 4, 2016 · 3 revisions

Event-raising collections are implementations of ICollection<T>, including IList<T> and ISet<T>, which additionally expose C♯ events which are triggered when the collection is modified. This is particularly useful where business logic must be executed whenever the state of the collection is changed.

The problem

A common pattern in implementation code, where the modification of a collection must trigger some business logic, is to hide the collection itself and instead expose modification methods:

public class Parent
{
  private IList<Child> _children;

  public Parent()
  {
    _children = new List<Child>();
  }

  public void Add(Child child)
  {
    // Custom business logic here
    _children.Add(child);
  }
}

This is well and good, but in doing so some of the flexibility of using a collection is lost. By hiding the actual collection and only exposing an Add method, we have hidden much of the functionality of IList<T>. What happens when we want to remove an item? We must also add a Remove method. What happens when we want to remove an item from a specified position? We must add a RemoveAt method. Over time, the needs of an application can easily take you down a path where you need to write custom implementations of all of IList<T> interface's members, in order to apply your business logic to each of them.

Event-raising collections provide a solution

Event-raising collections solve that problem by providing alternative implementations of ICollection<T>, IList<T> and ISet<T> which additionally expose the following events:

  • BeforeAdd
  • AfterAdd
  • BeforeRemove
  • AfterRemove

These events trigger before/after any action which modifies the state of the collection by adding/removing one or more items. They are triggered with every item added/removed and subscribers get to inspect the item and the state of the collection at that point in time. Subscribers may even modify the item/collection and - in the case of the 'before' events, cancel the modification altogether (although please use this functionality with caution).

Learn about how to use event-raising collections

Collection wrappers, for when replacement matters too

There is one type of 'modification' which is not covered by the above, and that is the act of replacing the whole collection with a different instance. If myColl is an event-raising collection, and I do the following:

myColl = new List<Child>();

… well, then there is no way to raise an event from this - because I have discarded the old instance and replaced it with a whole new object. Intercepting/overloading the assignment operator is impossible, and even if it were, it would not be a good idea. Instead, we use a wrapper object around our collection and use a property setter to replace the collection:

myWrapper.SourceCollection = new List<Child>();

This enables us to intercept the assignment and take appropriate action.

Learn about how to use event-raising collection wrappers

Binding shortcuts

To shorten the code in occasions when your needs are simple (and some limitations are OK), some extension methods are provided for binding to the 'modify collection' events. These work with either an event-raising collection wrapper or an event-raising collection itself. The methods are:

  • SetupActions
  • SetupAfterActions
  • SetupBeforeActions

SetupActions takes optional parameters indicating the actions to perform upon each of the four events are triggered. SetupBeforeActions and SetupAfterActions work in the same way except that they only deal with the before/after events respectively.

Please be aware that - as a limitation on this mechanism, it is not possible to unsubscribe from the events if listeners are added this way. If you need to be able to unsubscribe, or to receive the full EventArgs instances then please use the regular .NET event subscription mechanism instead. This convenience shortcut is intended only for simple scenarios in which their limitations are not a problem.