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

20

请看这个简单的LINQPad示例:

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

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

编译时出现以下错误:

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

我不明白这段代码存在什么逻辑问题,所有内容都可以静态验证。为什么不允许这种情况发生?它会导致语言上的一些不一致性吗,还是因为CLR的限制而被认为太过昂贵难以实现?如果是后者,作为一个开发人员,我应该了解哪些关于这个限制的信息?

考虑到接口支持它,我原本期望类也能从逻辑上跟进。


4
@Stilgar:您是否可以不要回答“由于基础CLR实现中的这个问题而不受支持”? - Chris
6
当然,我读了手册。但这仍然不能解释为什么“会很费力”。如果是因为实现细节的原因,那么了解一些实现细节将会很有帮助。 - Kendall Frey
5
也许你不关心具体的原因,但这并不意味着每个人都必须盲目地按照手册执行而不质疑任何设计决策。虽然一般来说,“为什么语言X不支持Y?”这样的问题的最直接、实用的回答是“因为没有人指定/实现它”,但如果有任何解释说明为什么它不像最初看起来那么好的想法,那么更好的答案肯定总会提供这样的解释。 - O. R. Mapper
5
我建议阅读Erik Lippert的文章为什么C#不实现顶级方法?,答案是:“答案总是相同的:因为没有人设计、规定、实现、测试、文档化和发布该功能。这六项都是使一个功能发生必不可少的事情。所有这些工作都需要大量的时间、精力和资金。功能并非廉价,我们非常努力地确保只发布那些给我们的用户带来最大可能的好处的功能...” - Erik Philips
5
那么为什么接口上进行“野生类型转换”是有意义的呢?你反对“野生类型转换”的论点我无法理解。 - Kendall Frey
显示剩余25条评论
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就很愚蠢”的程度。

非常好的例子。我猜你的意思是,如果我的例子能够工作,那么人们也会期望你的例子能够工作?我可以理解这可能会给语言设计者带来一些头痛。 - Kendall Frey
是的。他们必须允许我的示例工作(这是一项更困难的分析,必须考虑访问级别等因素),否则就要好好解释为什么不行。我们不允许工作的越多,程序员能够使用这种差异性的机会就越少。正如我所说,它肯定不会是无用的(对于很多不可变情况来说可能非常好),但成本效益肯定与变体接口有很大不同。 - Jon Hanna

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

2
这可以很容易地在编译时进行验证,就像我的示例一样。 - Kendall Frey
1
如果语言本身不支持深度不可变性,那么要保证这一点并不容易。请参考Eric Lippert在类似问题上的出色回答。 - alekseevi15
不一定需要不可变性。例如,任何类型为T的公共字段都将违反规则,并且T必须是不变的。可能有一些情况适用于协变或逆变,但它们仍然可能存在。 - Kendall Frey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接