Home > C#, Uncategorized, WPF > A Covariant ObservableCollection for .NET 3.x

A Covariant ObservableCollection for .NET 3.x

November 28th, 2008

When starting with generics, I was somewhat suprised that something like this didn’t work:

 

public interface IAnimal
{
}

public class Pig : IAnimal
{
}

public class AnimalFarm
{
  private ObservableCollection<Pig> pigs;

  public IEnumerable<IAnimal> Animals
  {
    get { return pigs; } //DOES NOT COMPILE
  }
}

 

The problem is that generics aren’t covariant, which is sometimes a bit of a problem when working with interfaces. However, while we’re waiting for C# 4.0, there is a poor man’s solution to covariance – the idea is to just expose the required IEnumerable<IAnimal> interface explicitly for the interface. And of course, there’s a generic solution to that problem:

 

/// <summary>
/// An implementation of <see cref="ObservableCollection{T}"/> that provides
/// an <see cref="IEnumerable{X}"/> interface for a super type of
/// <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type of items to be stored in the collection.</typeparam>
/// <typeparam name="X">A super type of <typeparamref name="X"/>, for which this
/// collection provides an <see cref="IEnumerable{X}"/> interface, thus providing
/// covariance.</typeparam>
public class HybridObservableCollection<T, X> : ObservableCollection<T>,
                                                IEnumerable<X> where T : X
{

  /// <summary>
  /// Provides enumeration over type <see cref="X"/>.
  /// </summary>
  /// <returns>A <see cref="IEnumerator{X}"/>> that can be used to iterate
  /// through the collection.</returns>
  IEnumerator<X> IEnumerable<X>.GetEnumerator()
  {
    foreach (T t in this)
    {
      yield return t;
    }
  }

}

 

Note the type constraint: The second type parameter (X) must be convertible to the first one (T), which ensures that you can’t break the collection.

And as a result, we can return the collection as both IEnumerable<Pig> or IEnumerable<IAnimal>:

public class AnimalFarm
{
  //the collection provides both IEnumerable<Pig>, IEnumerable<IAnimal>
  private HybridObservableCollection<Pig, IAnimal> pigs;

  public IEnumerable<IAnimal> Animals
  {
    get { return pigs; } //WORKS
  }

  public IEnumerable<Pig> Pigs
  {
    get { return pigs; } //WORKS TOO
  }
}

 

Of course, rather than just IEnumerable<IAnimal>, you could easily expose IList<IAnimal> that way, but you would risk runtime exceptions if somebody tried to inject another IAnimal implementation into the base class that is not an instance of type Pig. However, in a safe environment, this might be well feasible if it lets you expose your collections as lists and still stick to interfaces.


Author: Categories: C#, Uncategorized, WPF Tags:
  1. No comments yet.
  1. No trackbacks yet.