【问题标题】:Why doesn't C# support variant generic classes? [duplicate]为什么 C# 不支持变体泛型类? [复制]
【发布时间】:2015-05-23 22:35:40
【问题描述】:

以这个小的 LINQPad 为例:

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

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

编译失败,出现这个错误:

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

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

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

【问题讨论】:

  • @Stilgar:你能不能回答“因为底层 CLR 实现中的这个东西不支持它”?
  • @J...我当然读过手册。这仍然不能解释为什么它“会做很多工作”。如果是因为实现细节,最好至少了解一下该实现。
  • @J...:也许你对如何和为什么不感兴趣,但这并不意味着每个人都必须盲目地遵循手册而不质疑任何设计决策。虽然总的来说,对于诸如“为什么语言 X 不支持 Y?”之类的问题,最直接的实用答案确实是正确的。是“因为没有人指定/实现它。”,更好 的答案肯定总是提供解释,说明为什么它不像最初出现的那样好,如果有任何这样的解释可用。
  • 我建议阅读 Erik Lippert 的Why Doesn't C# ...?,答案: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 ...
  • @J... 那为什么它对接口有意义呢?您反对“狂野类型转换”的论点对我来说毫无意义。

标签: c# class generics generic-variance


【解决方案1】:

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

【讨论】:

  • 这可以在编译时轻松验证,就像我的示例一样。
  • 如果没有对语言的深度不变性支持,要保证这一点并不容易。请参阅similar question 上 Eric Lippert 出色答案的链接。
  • 不变性不是必须的。例如,任何类型为 T 的公共字段都将违反规则,并且 T 必须是不变的。可能存在差异适用的少数情况,但它们仍然存在。
【解决方案2】:

一个原因是:

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&lt;T&gt; 的任何实现都没有破坏协方差,因为它没有任何协方差。所需要做的就是确保没有使用T 作为参数(包括setter 方法的参数)并且已经完成。

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

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

【讨论】:

  • 很好的例子。我猜你的意思是,如果我的例子有效,那么人们会期望你的例子也有效吗?我知道这可能会给语言设计者带来一些麻烦。
  • 是的。他们要么允许我的示例工作(这是一个更困难的分析,必须考虑访问级别等),要么很好地解释为什么它不工作。而且我们不允许工作的越多,编码人员实际上可以使用这种差异的就越少。正如我所说,它肯定不会没用(在很多不可变的情况下,它确实可能非常好作为开始),但成本效益肯定与变体接口非常不同。
猜你喜欢
  • 1970-01-01
  • 2021-07-06
  • 2011-04-16
  • 2011-01-27
  • 1970-01-01
  • 2010-09-07
相关资源
最近更新 更多