为什么通用接口默认不是共同/逆变?

Kon*_*kus 16 c# generics covariance contravariance

例如IEnumerable<T>界面:

public interface IEnumerable<out T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}
Run Code Online (Sandbox Code Playgroud)

在此接口中,泛型类型仅用作接口方法的返回类型,不用作方法参数的类型,因此它可以是协变的.给出这个,不能编译从理论上推断出界面的方差吗?如果可以的话,为什么C#要求我们明确设置共同/反向变换关键字.

更新:正如Jon Skeet所提到的,这个问题可以用于子问题:

  1. 编译器可以通过它在当前泛型类型及其所有基类型中的使用方式来推断泛型类型的共同/逆变?

    例如.. .NET Framework 4.0中有多少通用接口参数可以自动标记为co/contravariant而没有任何歧义?约70%,80%,90%或100%?

  2. 如果可以的话,默认情况下是否应该对通用类型应用协变/反演?至少对于它能够分析和推断与类型使用的共同/逆转的那些类型.

Jon*_*eet 16

那么,这里有两个问题.首先,可以在编译器总是这样做呢?其次,应该(如果可以的话)?

对于第一个问题,我将推荐Eric Lippert,当我在第二版C#深度中提到这个问题时,他发表了这个评论:

即便如此,我也不清楚我们是否合情合理.我们可以很容易地提出需要对程序中的所有接口进行昂贵的全局分析来计算差异的情况,并且我们可以很容易地提出这样的情况,即它们 之间<in T, out U><out T, in U>之间无法做出决定.由于性能不佳和模糊的情况,这是一个不太可能的功能.

(我希望埃里克不介意我这个逐字引用;他之前非常善于分享这些见解,所以我要过去的形式:)

另一方面,我怀疑仍然存在可以推断出没有歧义的情况,所以第二点仍然相关......

我不认为它应该是自动的,即使编译器可以毫不含糊地知道它只在一种方式中有效.虽然在某种程度上扩展接口总是一个突破性的变化,但如果你是唯一一个实现它的人,通常不会.然而,如果人们都靠你的界面上进行变型,你可能不能够添加方法,它不会破坏客户......即使他们只是呼叫方,而不是执行者.您添加的方法可能会更改先前协变的接口以变为不变,此时您会破坏任何尝试共同使用它的呼叫者.

基本上,我认为要求这一点是明确的是好的 - 这是一个你应该有意识地做出的设计决定,而不是偶然地在不考虑协方差的情况下达到协方差/逆转.

  • @Koistya:将不变类型变为变体类型实际上是一个突破性的变化; 我们认为改变的好处值得休息的痛苦.请参阅我的文章:http://blogs.msdn.com/b/ericlippert/archive/2007/11/02/covariance-and-contravariance-in-c-part-nine-breaking-changes.aspx - 但是, Jon正确地指出,他所谈论的那种突破是*编辑界面可能会意外地改变其法律差异注释*.因此,我们希望将这些注释明确化,而不是推导它们. (3认同)
  • @Jon:Re"我希望Eric不介意......" - 引用我的朋友.我相信你引用我的背景.:-) (2认同)

Dar*_*rov 5

本文解释了编译器无法推断的情况,因此它为您提供了显式语法:

interface IReadWriteBase<T>
{
    IReadWrite<T> ReadWrite();
}

interface IReadWrite<T> : IReadWriteBase<T>
{

}
Run Code Online (Sandbox Code Playgroud)

你在这里推断什么,in或者out都工作?

  • 我通常不喜欢编译器代表我做出假设...如果存在可能的歧义,编译器应该*永远不会做出那种假设.您如何知道您的界面是协变还是逆变? (2认同)
  • @Thomas:的确如此.更糟糕的是,如果*other*接口继承自这个接口或者在形式参数或其方法的返回类型中有这个接口,那么*它们*的分析将根据这个是协变还是逆变而改变.以某种方式做出这种选择会产生非局部的影响. (2认同)