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

3

22

What is the real reason for that limitation? Is it just work that had to be done? Is it conceptually hard? Is it impossible?

Sure, one couldn't use the type parameters in fields, because they are allways read-write. But that can't be the answer, can it?

The reason for this question is that I'm writing an article on variance support in C# 4, and I feel that I should explain why it is restricted to delegates and interfaces. Just to inverse the onus of proof.

Update: Eric asked about an example.

What about this (don't know if that makes sense, yet :-))

public class Lookup<out T> where T : Animal {
  public T Find(string name) {
    Animal a = _cache.FindAnimalByName(name);
    return a as T;
  }
}

var findReptiles = new Lookup<Reptile>();
Lookup<Animal> findAnimals = findReptiles;

The reason for having that in one class could be the cache that is held in the class itself. And please don't name your different type pets the same!

BTW, this brings me to optional type parameters in C# 5.0 :-)

Update 2: I'm not claiming the CLR and C# should allow this. Just trying to understand what led to that it doesnt.

Bustee answered 29/3, 2010 at 21:40 Comment(9)
Sure, that's a reasonable example but you haven't shown anything that couldn't also be done with interfaces. Just make interface ILookup<out T> and have Lookup<T> implement it. What compelling additional benefit over interface variance does your scenario for class variance add?Frogman
None, actually. Besides that it is less code. Let me inverse the onus of proof. How could we explain why it is not supported. I'm not asking for implementing it, actually. "It has always been that way" doesn't count! :-)Bustee
We don't have to provide a justification for not implementing a feature. Not implementing a feature is free. Rather, we have to provide a justification for implementing a feature -- features can cost millions of dollars to Microsoft, and impose an even larger burden upon our customers who must then spend time and money learning about the feature.Frogman
@Eric Lippert: I can certainly imagine a usage case for covariant structures. How about KeyValuePair<T,U>? One could define an IKeyValPair<out T, out U>, and have a struct KeyValPair<T,U> which implements it, but that would entail really horrible boxing in a lot of usage scenarios.Jessie
@supercat: That is an excellent example of a type that could be made covariant. The key thing here is that the data type is logically immutable, and therefore you don't have to worry about fields changing their values after you set them in the constructor. If we were to have struct or class variance, that's where I'd want to start.Frogman
@Eric Lippert: I know you hate mutable structures (I just opened a chat room on the subject, btw) but would immutability be necessary for covariant correctness of structures? A List<Cat> which is passed to a routine expecting an IEnumerable<Animal> remains a List<Cat>, but a KeyValuePair<Cat, Apartment> which is passed to a routine expecting a KeyValuePair<Animal, Dwelling> would become a KeyValuePair<Animal, Dwelling>. Assuming Key were mutable, what harm could result from setting the key to a Dog? One would be storing the Dog in an Animal, not a Cat.Jessie
@Jessie so this would be some kind of covariant boxing for structs? that could work. classes should keep type and reference - then they have to be logically immutable...Bustee
@Lars Corneliussen: There are two types of mutable structs: those with mutating methods or property getters, and those which can only by mutated via property setters or public fields. When structs of the latter type are boxed, the system will mostly enforce immutability. With the former type, it doesn't. I don't share Eric Lippert's view that mutable structs are evil, but implicit boxing of self-mutating value-types is evil. Bonafide value types, mutable or not, could safely support covariance as value types but I'm not sure about boxed types. Some interesting issues there.Jessie
@Lars Corneliussen: I can certainly see problems if a structure implements any interfaces with generic types that aren't "out-qualified". On the other hand, if structure covariance had to be explicitly declared, that wouldn't be a problem. I can't think of any other cases in which a boxed struct could be used as something it wasn't without being unboxed; if the struct's types are covariant, the unboxing should convert the struct to the appropriate new type.Jessie
F
22

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.

Frogman answered 29/3, 2010 at 21:53 Comment(6)
Good question :) I remember a discussion between Anders and some Java compiler geek (sorry) at the Lang.NET last year, where the Java-guy asked for this feature. He seemed to knwo why he asked. But I can't remember. I thought of classes without state. I'll try to make something up in the question.Bustee
This would be definitely a nice feature for immutable data types such as Tuple<T1, T2>, but I agree that this isn't convincing enough for changing the CLR :-).Bookseller
Another use case is phantom types where the T is only used for type-safety and not in the implementation of the class.Freemanfreemartin
Aren't Lazy<T> and Task<T> classes suitable to be covariant on T? They use T only in out position in their public API.Romeliaromelle
Lots of examples. How about Builders? Certainly those are candidates for both covariant and contravariant generics. The notion that a class's state precludes it from benefiting from generic variance seems pretty flimsy. It only holds when state corresponds with a type var, which is entirely dependent on the class at hand. In other words it's not hard to imagine a class that produces Ts or consumes Ts, but not both. And because you can factor out an interface, doesn't in my view imply classes shouldn't be allowed to have variance. shrugMango
With primary constructors and readonly fields this can be entirely safe, cf Scala. I think the former were scheduled for C# 7.Shake
B
8

As far as I know, this feature isn't supported by CLR, so adding this would require significant work on the CLR side as well. I believe that co- and contra-variance for interfaces and delegates was actually supported on CLR before the version 4.0, so this was a relatively straightforward extension to implement.

(Supporting this feature for classes would be definitely useful, though!)

Bookseller answered 29/3, 2010 at 21:45 Comment(1)
You're right. Variance in generic type params of interfaces and delegates came with CLR 2.0 - just not in C#Bustee
J
1

If they were permitted, useful 100% type-safe (no internal typecasts) classes or structures could be defined which were covariant with regard to their type T, if their constructor accepted one or more T's or T supplier's. Useful, 100%-type-safe classes or structures could be defined which were contravariant with respect to T if their constructors accepted one or more T consumers. I'm not sure there's much advantage of a class over an interface, beyond the ability to use "new" rather than using a static factory method (most likely from a class whose name is similar to that of the interface), but I can certainly see usage cases for having immutable structures support covariance.

Jessie answered 24/8, 2011 at 18:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.