采用这个小LINQPad示例:
void Main()
{
Foo<object> foo = new Foo<string>();
Console.WriteLine(foo.Get());
}
class Foo<out T>
{
public T Get()
{
return default(T);
}
}
无法使用此错误进行编译:
无效的方差修饰符。只能将接口和委托类型参数指定为变体。
我没有看到代码存在任何逻辑问题。一切都可以静态验证。为什么不允许这样做?它是否会导致语言不一致,或者由于CLR的限制而被认为实施起来太昂贵了?如果是后者,我作为开发人员应该知道上述限制吗?
考虑到接口支持它,我希望从逻辑上遵循该类支持。
答案 0 :(得分:10)
一个原因是:
class Foo<out T>
{
T _store;
public T Get()
{
_store = default(T);
return _store;
}
}
此类包含一个不协变的功能,因为它有一个字段,字段可以设置为值。它虽然以协变的方式使用,因为它只被分配了默认值,并且对于任何实际使用协方差的情况,它只会是null
。
因此我们不清楚是否可以允许它。不允许它会激怒用户(它毕竟符合你建议的相同潜在规则),但允许它很难(分析已经变得有点棘手了,我们甚至没有开始寻找真正棘手的案例)
另一方面,对此的分析要简单得多:
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;
}
}
很容易确定IFoo<T>
的任何实现都没有打破协方差,因为它没有得到任何协方差。所有必要的是确保没有使用T
作为参数(包括setter方法的参数)并完成它。
由于类似的原因,潜在的限制在类上比在接口上更加艰巨,这也降低了协变类有用的程度。它们当然不会毫无用处,但它们对于指定和实施关于它们将被允许做什么的规则的工作量的有用程度的平衡要远远小于协调如何有用的平衡。接口是关于如何指定和实现它们的工作量。
当然,差异就足以让它超越&#34;好吧,如果你要允许X,那么不允许Y会很愚蠢......&#34;。< / p>
答案 1 :(得分:0)
一个类只需要包含输出方法参数(为了协变)和仅包含输入方法参数(为了逆变)。关键在于,很难保证对于类:例如,协变类(通过T类型参数)不能包含T字段,因为您可以写到这些字段。它对于真正不可变的类很有用,但目前还没有全面支持C#中的不变性(比如在Scala中)。