Why doesn't C# support variant generic classes? [duplicate]
Asked Answered
D

2

21

Take this small LINQPad example:

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

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

It fails to compile with this error:

Invalid variance modifier. Only interface and delegate type parameters can be specified as variant.

I don't see any logical problem with the code. Everything can be statically verified. Why is this not allowed? Would it cause some inconsistency in the language, or was it deemed too expensive to implement due to a limitation in the CLR? If it is the latter, what should I as a developer know about said limitation?

Considering that interfaces support it, I would have expected class support to logically follow from that.

Declaim answered 20/3, 2015 at 13:37 Comment(30)
It is because of a CLR limitation. Now it would be cool if you modify your question so someone can answer why there is a CLR limitation on this.Summerwood
@Stilgar: Could you not answer "It is not supported because of this thing in the underlying CLR implementation"?Marna
Classes need to worry about implementation details - delegates and interfaces don't. Making this work would be a lot of work. Asking why nobody on the C# team did this work seems a bit pointless - they didn't, it's not supported, end of story.Fives
@Fives Of course I read the manual. That still doesn't explain why it "would be a lot of work." If it's because of implementation details, it would be nice to know at least a little bit about that implementation.Declaim
@J...: Maybe you're not interested in hows and whys, but that doesn't mean everyone has to mindlessly follow a manual without questioning any design decisions. While in general, it is true that the most straightforward practical answer to questions like "Why does language X not support Y?" is "Because no-one specified/implemented it.", the better answer is certainly always one that provides explanations as to why it would not be as good an idea as it first appears, if any such explanations are available.Edan
This question does not have a direct relation on creating or supporting code. Why developers choice to or not to include a feature in a framework or langauge is not directly useful in creating a code solution (at all). Where-as asking about how a feature works or how to use an IDE addon directly relate to the creation of a code solution.Narva
I suggest reading Erik Lippert's Why Doesn't C# ...?, answer: The answer is always the same: because no one ever designed, specified, implemented, tested, documented and shipped that feature. All six of those things are necessary to make a feature happen. All of them cost huge amounts of time, effort and money. Features are not cheap, and we try very hard to make sure that we are only shipping those features which give the best possible benefits to our users given ...Narva
@O.R.Mapper If that's the case then the question should be What would be required for C# to support X feature, in which case the question is very much too broad and off-topic.Fives
@ErikPhilips Indeed. Generics to me are all about type safety and writing clean, organized, well designed code. What OP is suggesting is anathema to the entire design philosophy of C# - wild typecasting of this sort makes for nightmarish code. I can't see why the designers would want to encourage that sort of thing.Fives
@Fives Then why does it make sense for interfaces? Your argument against "wild typecasting" makes no sense to me.Declaim
@Fives me either but it doesn't matter. Any question of why some framework/langauge doesn't support X is in my opinion off topic because it doesn't help a developer write code any more than asking why the trunk of my car does not have a way to open the trunk from the inside affects my ability to drive my car.Narva
@ErikPhilips This does directly impact developers, because this feature might be useful. You could achieve the same result with an interface, but if the only reason to use interfaces is to support variance, one is likely to wish they could use variance without interfaces.Declaim
There are other reasons than "no one implemented it" for example it may be provably impossible to implement. I am quite curious what would need changing in the CLR to support this.Summerwood
@Summerwood If it is provably impossible, that is why I asked the question, because I don't know of anything in the language that would make it impossible. If there is, I certainly want to be aware of it.Declaim
@KendallFrey You question is Why doesn't C# support variant generic classes?, that answer does NOT directly impact develpers, it has NO impact. If you want a On Topic question, it would be If c# does not support variant generic classes natively how can I emulate it.Narva
The question makes perfect sense except that it can be answered with "It is a limitation of the CLR" which is not very interesting. It is interesting what this limitation is and can it be removed.Summerwood
@ErikPhilips Some of us like to understand what makes things tick, rather than just how to make them tick. If I gain any understanding about C# or the CLR through an answer to this question, I will have become a better developer.Declaim
@KendallFrey Some of us like to understand what makes things tick I agree and do so myself, I have aspnetwebstack.codeplex.com memorized for that very reason. I'l state this again, the answer to your question in the title will most likely yeild a reason like, as I quoted Eric Lippert: In this particular case, the clear user benefit was in the past not large enough to justify the complications to the language which would ensue.. That answer has no direct code benefit, it simply wasn't prioritized above other features. Instead ask how you can emulate the feature.Narva
@ErikPhilips In that case, the answer would still teach me that there is no major barrier. If that's the case, then I can rest content in the fact that it's just not implemented, and hope that someday it will be, rather than spending time puzzling over it myself.Declaim
see also : https://mcmap.net/q/103577/-does-c-support-return-type-covarianceFives
and... https://mcmap.net/q/103811/-why-c-doesn-39-t-allow-inheritance-of-return-type-when-implementing-an-interfaceFives
and... https://mcmap.net/q/103576/-c-covariance-on-subclass-return-typesFives
@J...: All those links are about return type covariance. This question is actually about generic class covariance.Gretna
The more relevant link is https://mcmap.net/q/661238/-net-4-0-covariance/…. This question is a duplicate of that one.Gretna
@EricLippert Yes, but because return type covariance is not supported in the CLR I suppose I meant to imply that generic class covariance would necessarily require uncomfortable restrictions of the sort that one would not expect a class to be held to.Fives
@J...: I'm not following your train of thought here. A reason to not support return type covariance is because it creates "brittle base class" problems. (There are others of course.) A reason to not support generic class covariance is because it implies that the class must be immutable, and we don't have a good way to represent that. I don't see how the two are connected.Gretna
@KendallFrey: There is a major barrier to having covariant generic classes: every type Foo<T> has its own independent set of static members. If a reference to a MyClass<SiameseCat> were cast to a MyClass<Animal>, whose static members should it access? It might be possible for the CLR to support covariance for generic classes meeting some very strict criteria (e.g. no static members) but defining and testing for the necessary criteria would be a lot of work in and of itself.Avar
@EricLippert Then my train of thought is probably wrong. I presumed a prerequisite where none existed. Your point is clear and well taken - thanks!Fives
@Avar Wow, I hadn't thought of that, that's a good point.Declaim
@supercat: The problem is somewhat mitigated by C# not allowing you to call static methods off of an instance expression as the receiver.Gretna
F
11

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…".

Finale answered 20/3, 2015 at 16:52 Comment(2)
Very good example. I guess what you're saying is that if my example worked, then people would expect your example to work as well? I can see how that might cause some headaches for language designers.Declaim
Yes. They'd have to either allow my example to work (which is a much more difficult analysis, that has to consider access levels and so on) or else nicely explain why it doesn't. And the more we don't allow to work, the less coders can actually use this variance for. As I said, it certainly wouldn't be useless (it could be very nice indeed in a lot of immutable cases for a start), but the cost-benefit is definitely very different to that of variant interfaces.Finale
A
0

A class would need to contain only output method parameters (in order to be covariant) and only input method parameters (in order to be contravariant). The point is that it's hard to guarantee that for classes: for example, covariant class (by T type parameter) cannot have fields of T, because you could write to those fields. It would work great for truly immutable classes, but there is no a comprehensive support for immutability in C# at the moment (say, as in Scala).

Allout answered 20/3, 2015 at 15:47 Comment(3)
This could easily be verified at compile time, as with my example.Declaim
It's not so easy to guarantee that without a deep immutability support in a language. See link to the Eric Lippert's excellent answer on a similar question.Allout
Immutability isn't necessarily required. For example, any public field of type T would be a violation of the rules, and T would have to be invariant. There may be few situations where variance applies, but they could still exist.Declaim

© 2022 - 2024 — McMap. All rights reserved.