Tuesday, November 03, 2009

Covariance and contravariance

I recently upgraded an "asynchronous observable framework" (working title: Olib) to .NET 4.0 beta 2. The reason for this was the new "reactive" pieces (IObservable<> et al.) but I ended up not using them since I needed a different interaction pattern (more of that in a future post). There wasn't anything more I wanted to use, but found a few interesting pieces which dramatically helped me:
Covariance and contravariance have been implemented for some time in the .NET world, in various forms (parameters, delegates etc.). This new feature makes some seemingly minor additions to generic interfaces and generic delegates (Func<> and Action<>). eg:

interface IEnumerable<out T>

This "out" keyword denotes the type as only usable as output within the interface, and therefore considered contravariant. This means that you can do:
IEnumerable<string> i = new[] { "0", "1", "2", "3" }; IEnumerable<object> o = i;
Note you cannot do anything that requires anything other than the most basic cast, eg. boxing:
IEnumerable<int> i = new[] { 0, 1, 2, 3 };
IEnumerable<object> o = i; // cannot implicitly convert
One of the best things about this (and the reason I used it for Olib) was that you can write much more powerful extension methods now, since code you write to operate on "IEnumerable" will now work with any enumerable whose items support IComparable.

For example, in Olib I'm creating Linq aggregates:
public static IAsyncObservableValue<TInner> Sum<TInner>(this IAsyncObservableSet<IAsyncObservableValue<TInner>> innerSet)
Using the right contravariant constraints (and I have to admit a major refactor to make it confirm to the rules), I can now use this extension method on types such as AsyncObservableSet<asyncobservablevalue<t>> - as the types inherit from their respective interfaces.

Tip: I found in some cases where I could not easily comply with the contravariant constraints, I split my interface into two - one "read only" (contravariant) interface and one "read/write" (invariant).

Can't go back to .NET 3.5 now.

0 comments: