14 September 2011

Covariance contravariance inconvenience

I love the covariance/contavariance support in C#4. That is the mechanism that lets you implicitly convert from an IEnumerable<Child> to an IEnumerable<Parent> was expected (covariance), and an IComparable<Parent> can be converted to IComparable<Child> (contravariance). Here Child obviously inherits from Parent.

One of the biggest problems though is that this only works for classes and interfaces where the type parameters are either all outbound or all inbound for covariance and contravariance respectively. E.g. no methods on IEnumerable<T> accept T, which is covariance. And no methods on IComparable<T> return T. Eric Lippert wrote a bunch of posts worth reading about while the compiler team were considering variance.

This is somewhat inconvenient. For example it means that IList<Child> cannot be assigned to IList<Parent>, or vice-versa, because IList<T> methods both accept and return T. And often one may wish to write a method using IList<T> rather than IEnumerable<T> as it allows for direct element access. It is particularly frustrating because I may well only need to use the methods that read from the list, and so if IReadonlyList<T> existed then all might be sweet.

Now I was wrestling with this kind of thing the other day when I read Lippert's post on What is this thing called a Type, which got me thinking about it all again. Here he's again talking about how various operations tranform one Type into another.

OK, to the point. Here's the idea I was thinking. What if it were possible to create modified types based on existing interfaces and classes that only present the input or output methods.

For example (and to use Lippert's Giraffe inherits from Mammal inherits from Animal example):
IList<out Mammal> and IList<in Mammal> becomes types that are declarable in code.

IList<out Mammal> exposes only the subset of IList<Mammal> that is covariant. I.e. the methods for reading from the list, but none of the methods that accept T parameters. IList<out Giraffe> could be assigned to IList<out Mammal>. As a type, IList<out Mammal> itself would be smaller than IList<Mammal>, so it is always possible to convert from IList<Mammal> to IList<out Mammal>. So, finally IList<Giraffe> could be assigned to IList<out Mammal>.

Likewise, IList<in Mammal> exposes only the subset of IList<Mammal> that is contravarient, exposing only the methods for modifying a list. By the same (but contravariant) argument, IList<Giraffe> would be assignable to IList<in Mammal>.

Some code we could then write:

static void FeedMammals(IList<out Mammal> mammals)
{
// mammals.Count is available as it doesn't use the type parameter.
// Using an old-style for-loop to illustrate we don't want to use IEnumerable<T>
for (int i=0; i<mammals.Count; i++) parameter
{
// only the getter is available, as it is outbound
mammals[i].FeedMammalFood();
}

// But these are illegal:
mammals[0] = new Mammal(); // setter
mammals.Insert(0, new Mammal()); // method that accepts T
}


And call with:

List<Giraffe> giraffes = ...;
FeedMammals(giraffes); // yay, cast from List<Giraffe> to IList<out Mammal>


Similarly we could have:

static void AddBabyMammals(IList<in Mammal> mammals)
{
// New baby born
Mammal baby = GetNewBabyMammal();
mammals.Insert(0, baby);

//Setter is also OK to replace a value
mammals[0] = baby;

// But these would be illegal
Mammal m1 = mammals[0]; // getter
foreach (var m2 in mammals) // call to enumarator, because its 'out'
}


And call with:

List<Animal> animals = ...;
AddBabyMammals(animals); // yay, cast from List<Giraffe> to IList<out Mammal>


This would be a wonderful feature. Anyway, that's my two cents.

1 comment:

Tim S said...

Much of my relationship to C# 4 is dictated by this same situation, Task parallels, Dynamic language features (I have an awesome Row object which auto-populates its properties from a generic IDataReader, which makes quick and dirty ETL awesome). All of which are capital A Awesome, but would be great if they just did 'x' as well.

I've no doubt these features will get tidied up in future releases, I should probably follow your lead and these up when I stumble across them.