为什么C#不支持变体generics类?

拿这个小LINQPad示例:

void Main() { Foo foo = new Foo(); Console.WriteLine(foo.Get()); } class Foo { public T Get() { return default(T); } } 

它无法使用此错误进行编译:

方差修饰符无效。 只能将接口和委托类型参数指定为变量。

我没有看到代码的任何逻辑问题。 一切都可以静态validation。 为什么不允许这样做? 它是否会导致语言不一致,或者由于CLR的限制而被认为实施起来太昂贵了? 如果是后者,我作为开发人员应该知道什么是上述限制?

考虑到接口支持它,我希望从逻辑上遵循它的类支持。

一个原因是:

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

此类包含一个不协变的function,因为它有一个字段,字段可以设置为值。 它虽然以协变的方式使用,因为它只被分配了默认值,并且对于任何实际使用协方差的情况,它只会变为null

因此,目前尚不清楚我们是否可以允许它。 不允许它会激怒用户(它毕竟符合你建议的相同潜在规则),但允许它很难(分析已经变得有点棘手了,我们甚至没有开始寻找真正棘手的案例)。

另一方面,对此的分析要简单得多:

 void Main() { IFoo foo = new Foo(); Console.WriteLine(foo.Get()); } interface IFoo { T Get(); } class Foo : IFoo { T _store; public T Get() { _store = default(T); return _store; } } 

很容易确定IFoo的实现都没有打破协方差,因为它没有任何协方差。 所有必要的是确保不使用T作为参数(包括setter方法的参数)并完成它。

由于类似的原因,潜在限制在类上比在接口上更加艰巨,这也降低了协变类有用的程度。 它们当然不会毫无用处,但它们对于指定和实施关于它们将被允许做什么的规则的工作量的有用程度的平衡要远远小于协变接口的有用性的平衡。关于如何指定和实施它们的工作量。

当然,差异足以让它超过“好吧,如果你要允许X,那么不允许Y ……就是愚蠢的”。

类需要只包含输出方法参数(为了协变)和仅包含输入方法参数(为了逆变)。 关键是很难保证对于类:例如,协变类(通过T类型参数)不能有T字段,因为您可以写入这些字段。 它对于真正不可变的类很有用,但目前还没有全面支持C#中的不变性(比如在Scala中)。