Why Isn't There Generic Variance for Classes in C# 4.0

Why isn't there generic variance for classes in C# 4.0?

Suppose you had a class C<T> that was covariant in T. What might its implementation look like? T has to be out only. That means that C<T> cannot have any method that takes a T, any property of type T with a setter, or any field of type T, because fields are logically the same as property setters; T goes in.

Pretty much the only useful thing you could build with a covariant class is something immutable as far as T is concerned. Now, I think it would be awesome to have covariant immutable lists and stacks and whatnot that were class types. But that feature is not so obviously awesome that it would clearly justify the massive expenditure in making the type system natively support covariant immutable class types.

A comment above asked for an example of where this would be useful. Consider the following sketch:

sealed class Stack<out T>
{
private readonly T head;
private readonly Stack<T> tail;
public T Peek() { return head; }
public Stack<T> Pop() { return tail; }
public Stack(T head, Stack<T> tail)
{
this.tail = tail;
this.head = head;
}
}
static class StackExtensions
{
public static Stack<T> Push<T>(this Stack<T> tail, T head)
{
return new Stack<T>(head, tail);
}
public static bool IsEmpty<T>(this Stack<T> stack)
{
return stack == null;
}
}

Suppose you had covariant classes. Now you can say

Stack<string> strings = null;
strings = strings.Push("hello");
strings = strings.Push("goodbye");
Stack<object> objects = strings;
objects = objects.Push(123);

And hey, we just pushed an integer onto a stack of strings, but everything worked out just fine! There's no reason why this couldn't be typesafe. An operation which would violate type safety on a mutable data structure can be safely covariant on an immutable data structure.

Why doesn't C# support variant generic classes?

One reason would be:

class Foo<out T>
{
T _store;
public T Get()
{
_store = default(T);
return _store;
}
}

This class contains a feature that is not covariant, because it has a field, and fields can be set to values. It is though used in a covariant way, because it is only ever assigned the default value and that is only ever going to be null for any case where covariance is actually used.

As such it's not clear if we could allow it. Not allowing it would irritate users (it does after all match the same potential rules you suggest), but allowing it is difficult (the analysis has gotten slightly tricky already and we're not that even beginning to hunt for really tricky cases).

On the other hand, the analysis of this is much simpler:

void Main()
{
IFoo<object> foo = new Foo<string>();
Console.WriteLine(foo.Get());
}

interface IFoo<out T>
{
T Get();
}

class Foo<T> : IFoo<T>
{
T _store;
public T Get()
{
_store = default(T);
return _store;
}
}

It's easy to determine that none of the implementation of IFoo<T> breaks the covariance, because it hasn't got any. All that's necessary is to make sure that there is no use of T as a parameter (including that of a setter method) and it's done.

The fact that the potential restriction is a lot more arduous on a class than on an interface for similar reasons, also reduces the degree to which covariant classes would be useful. They certainly wouldn't be useless, but the balance of how useful they would be over how much work it would be to specify and implement the rules about what they would be allowed to do is much less than the balance of how useful covariant interfaces are over how over how much work it was to specify and implement them.

Certainly, the difference is enough that it's past the point of "well, if you're going to allow X it would be silly to not allow Y…".

Why does C# (4.0) not allow co- and contravariance in generic class types?

First off, as Tomas says, it is not supported in the CLR.

Second, how would that work? Suppose you have

class C<out T>
{ ... how are you planning on using T in here? ... }

T can only be used in output positions. As you note, the class cannot have any field of type T because the field could be written to. The class cannot have any methods that take a T, because those are logically writes. Suppose you had this feature -- how would you take advantage of it?

This would be useful for immutable classes if we could, say, make it legal to have a readonly field of type T; that way we'd massively cut down on the likelihood that it be improperly written to. But it's quite difficult to come up with other scenarios that permit variance in a typesafe manner.

If you have such a scenario, I'd love to see it. That would be points towards someday getting this implemented in the CLR.

UPDATE: See

Why isn't there generic variance for classes in C# 4.0?

for more on this question.

Why aren't classes in .NET 4 covariant?

Eric Lippert answers the first part of you question here.

As for the second part of you question, return type covariance has been requested for both C# and VB.NET - however it has not yet been prioritized high-enough relative to other language features to make it into any release. If I remember correctly, this feature would also require changes to the CLR to be implemented appropriately.

As to why this is so, I'll channel Eric Lippert for a moment and respond that not implementing a feature is free, while implementing a features requires the budget and time to design, develop, test, and document it ... which is not free. A feature has to be sufficiently valuable to enough people to justify the expense of creating it.

Why is C# 4.0's covariance/contravariance limited to parameterized interface and delegate types?

Simple answer: it's a CLR limitation.

(I haven't seen a good, concrete explanation for this anywhere... I don't remember seeing one in Eric's blog series about it, although I may well have missed it somewhere.)

One thing I would say is that both delegates and interfaces already form "layers of indirection" over the real types; views on methods or classes, if you will. Changing from one view to another view is fairly reasonable. The actual class feels like a more concrete representation to me - and shifting from one concrete representation to another feels less reasonable. This is a very touchy-feely explanation rather than a genuine technical limitation though.

Class hierarchy problem (with generic's variance!)

On the line where you get an error, you're trying to return IList<IStates> as if it was an instance of type IList<ITask>. This doesn't work automtaically, because the two types are different (no matter that the generic parameters are related).

In C# 3.0 or older, there is no way to achieve that automatically. C# 4.0 adds support for covariance and contravariance, which serves exactly this purpose. But as you noted, this works only when the returned collection is read-only. The IList<T> type doesn't guarantee that, so it isn't annotated as covariant in .NET 4.0.

To make this work using C# 4.0, you'll need to use truly read-only type, which has covariant annotation in the framework - the best option in your case is IEnumerable<T> (although you could define your own using the out T modifier).

To add more details, in C# 4.0, you can declare an interface as either covariant or contra-variant. The first case means that the compiler will allow you to perform the conversion you needed in your example (the other case is useful for write-only classes). This is done by adding explicit annotations to the interface declaration (these are already available for .NET 4.0 types). For example, the declaration of IEnumerable<T> has the out annotation meaning that it supports covariance:

public interface IEnumerable<out T> : IEnumerable { /* ... */ }

Now, the compiler will allow you to write:

IEnumerable<IState> states = ...
IEnumerable<ITask> tasks = states;


Related Topics



Leave a reply



Submit