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.