A Covariant ObservableCollection for .NET 3.x
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.