19

以这个小的 LINQPad 为例:

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

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

它无法编译并出现此错误:

无效的方差修饰符。只有接口和委托类型参数可以指定为变体。

我看不出代码有任何逻辑问题。一切都可以静态验证。为什么不允许这样做?它会导致语言上的一些不一致,还是由于 CLR 的限制而被认为实施起来过于昂贵?如果是后者,作为开发人员,我应该对上述限制了解多少?

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

4

2 回答 2

11

一个原因是:

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 方法的参数)并且它已经完成。

由于类似的原因,类的潜在限制比接口上的潜在限制要困难得多,这一事实也降低了协变类的有用程度。它们当然不会毫无用处,但是它们的有用性与指定和实施关于它们将被允许做什么的规则的工作量之间的平衡远小于协变接口的有用性之间的平衡。关于指定和实施它们的工作量。

当然,差异已经足够,以至于它已经过了“好吧,如果你要允许 X 不允许 Y ......”的观点。

于 2015-03-20T16:52:59.677 回答
0

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

于 2015-03-20T15:47:38.373 回答