C# 为什么通用接口在默认情况下不是共变/逆变的?

C# 为什么通用接口在默认情况下不是共变/逆变的?,c#,generics,covariance,contravariance,C#,Generics,Covariance,Contravariance,例如IEnumerable接口: public interface IEnumerable<out T> : IEnumerable { IEnumerator<T> GetEnumerator(); } 公共接口IEnumerable:IEnumerable { IEnumerator GetEnumerator(); } 在此接口中,泛型类型仅用作接口方法的返回类型,而不用作方法参数的类型,因此它可以是协变的。考虑到这一点,编译器从理论上不能从接口推断出差

例如
IEnumerable
接口:

public interface IEnumerable<out T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}
公共接口IEnumerable:IEnumerable
{
IEnumerator GetEnumerator();
}
在此接口中,泛型类型仅用作接口方法的返回类型,而不用作方法参数的类型,因此它可以是协变的。考虑到这一点,编译器从理论上不能从接口推断出差异吗?如果可以,为什么C#要求我们显式设置协方差/逆变关键字

更新:正如Jon Skeet提到的,这个问题可以分为几个子问题:

  • 编译器能否通过在当前泛型类型及其所有基类型中如何使用泛型类型来推断泛型类型的协变/逆变

    例如。。有多少来自.NET Framework 4.0的通用接口参数可以在没有任何歧义的情况下自动标记为co/contravariant?大约70%、80%、90%还是100%

  • 如果可以,是否默认对泛型类型应用协方差/逆变?至少对于那些能够从类型使用中分析和推断协方差/逆方差的类型


  • 这里有两个问题。首先,编译器是否总是这样做?第二,是否应该(如果可以的话)

    关于第一个问题,我将听取Eric Lippert的意见,他是在我在《C#Depth》第二版中提出这一问题时发表这一评论的:

    即使我们愿意,我也不清楚我们是否有理由这样做。我们可以很容易地找到 在需要对程序中的所有接口进行昂贵的全局分析的情况下 为了计算出方差,我们可以很容易地得出这样的情况,即
    ,无法在两者之间做出决定。两者都不好 性能和不明确的情况下,这是一个不太可能的功能

    (我希望Eric不介意我一字不差地引用这句话;他以前非常善于分享这些见解,所以我现在就用过去的形式:)

    另一方面,我怀疑仍然有一些情况下,它可以推断没有歧义,所以第二点仍然是相关的

    我不认为它应该是自动的,即使编译器可以明确地知道它在一种方式上是有效的。虽然在某种程度上,扩展接口总是一个突破性的改变,但如果只有您一个人实现了它,通常情况下就不是这样了。然而,如果人们依赖于你的界面是可变的,你可能无法在不破坏客户端的情况下向它添加方法。。。即使他们只是呼叫者,而不是实现者。您添加的方法可能会将以前的协变接口更改为不变接口,在这一点上,您会中断任何尝试协变使用它的调用方

    基本上,我认为要求明确是好的-这是一个你应该有意识地做出的设计决策,而不是在没有考虑的情况下意外地得到协方差/逆变。

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

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

    你在这里推断出什么中的
    中的
    都是有效的?

    如果使泛型类型协变/逆变可能导致“破坏客户端”,那么为什么.NET创建者在.NET 4.0中将其主要泛型接口替换为协变/逆变接口?@Koistya:不是这样的:相反。如果它被推断为协变的,但是你添加了一个使它不变的方法,那么任何使用协变的客户端都会被破坏。我将编辑以解释这一点。@Koistya:将不变类型转换为变体类型实际上是一个突破性的改变;我们认为改变的好处值得休息的痛苦。请参阅我的文章:-然而,Jon正确地指出,他所说的中断类型是编辑接口可能会意外地更改其法律变更注释。因此,我们希望将这些注释明确化,而不是推断出来。@Jon:Re“我希望Eric不介意…”引用我的朋友的话。我相信你会在上下文中引用我的话。:-)您能想到任何一个例子,其中依赖于泛型接口类型的客户端是不变的。如果将generic从不变量更改为variant是一个突破性的更改,那么受此更改影响的代码百分比是多少?换句话说,有多少百万的开发者会抱怨它编译器可以根据用法将
    out
    关键字默认应用于基本接口,对于基于父TYep的派生接口也可以应用此关键字,您可以手动设置两者,但从编译器的角度来看,在您的示例中,默认情况下可以将T设置为协变。是还是否?是的,但是如果编译器做出了这个决定,并且我想在
    中使用
    ,那么在这种情况下,我们有一个冲突,只有通过明确的方式才能解决。我通常不喜欢编译器代表我做假设。。。如果可能存在歧义,编译器永远不应该做出这种假设。你怎么知道你的界面是协变的还是逆变的?@Thomas:的确如此。更糟糕的是,如果其他接口继承自此接口,或者在其方法的形式参数或返回类型中包含此接口,那么对它们的分析将根据此接口是协变的还是逆变的而变化。做出这样或那样的选择有着非地方性的影响。好问题。我在2007年设计此功能时就预料到了您的问题。这就是为什么我当时写了一篇文章来回答这个问题:关于为什么这不是自动的,下面有一些很好的答案。我只是想补充一点,ReSharper会建议协方差/逆方差,如果您接受它的建议,甚至会为您进行重构。我不知道在这种情况下效果如何/