在C#中理解协方差与泛型的协方差的问题

Ben*_*Laz 115 c# covariance contravariance

我无法理解为什么以下C#代码无法编译.

正如您所看到的,我有一个静态泛型方法Something with a IEnumerable<T>parameter(并且T被约束为一个IA接口),并且此参数不能隐式转换为IEnumerable<IA>.

解释是什么?(我不寻找解决方法,只是为了解它为什么不起作用).

public interface IA { }
public interface IB : IA { }
public class CIA : IA { }
public class CIAD : CIA { }
public class CIB : IB { }
public class CIBD : CIB { }

public static class Test
{
    public static IList<T> Something<T>(IEnumerable<T> foo) where T : IA
    {
        var bar = foo.ToList();

        // All those calls are legal
        Something2(new List<IA>());
        Something2(new List<IB>());
        Something2(new List<CIA>());
        Something2(new List<CIAD>());
        Something2(new List<CIB>());
        Something2(new List<CIBD>());
        Something2(bar.Cast<IA>());

        // This call is illegal
        Something2(bar);

        return bar;
    }

    private static void Something2(IEnumerable<IA> foo)
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

错误我Something2(bar)排队:

参数1:无法从'System.Collections.Generic.List'转换为'System.Collections.Generic.IEnumerable'

Eri*_*ert 218

错误信息信息不足,这是我的错.对于那个很抱歉.

您遇到的问题是协方差仅适用于引用类型的结果.

你现在可能正在说"但是它IA是一种参考类型".是的.但你没有说这T 等于 IA.你说的T是哪一种类型的实现 IA,和值类型可以实现一个接口.因此,我们不知道协方差是否有效,我们不允许它.

如果你想要协方差工作,你必须告诉编译器类型参数是带有class约束的参考类型以及IA接口约束.

错误信息确实应该说转换是不可能的,因为协方差需要保证引用类型,因为这是根本问题.

  • @ user4951:因为我实现了所有转换检查逻辑,包括错误消息. (76认同)
  • 为什么你说这是你的错? (3认同)
  • @ PeterA.Schneider:我很感激.但是,我在Roslyn中设计错误报告逻辑的主要目标之一,特别是不仅要捕获违反了哪个规则,而且要尽可能地识别"根本原因".例如,错误消息应该是什么`customers.Select(c => c.FristName)`?C#规范非常清楚这是一个*重载解析*错误:名为Select的适用方法集可以使该lambda为空.但根本原因是`FirstName`有拼写错误. (3认同)
  • @ PeterA.Schneider:我做了很多工作来确保涉及泛型类型推断和lambdas的场景使用适当的启发式来推断出哪些错误消息可能最有助于开发人员.但是我在转换错误消息方面做得不那么好,特别是在涉及差异的情况下.我一直很后悔. (3认同)

Mar*_*oth 26

我只是想用一些代码示例来补充Eric的优秀内部答案,这些代码示例可能不熟悉泛型约束.

改变这样Something的签名:class约束必须先行.

public static IList<T> Something<T>(IEnumerable<T> foo) where T : class, IA
Run Code Online (Sandbox Code Playgroud)

  • @TomWright - 规范当然不包括许多"为什么?"的答案.问题,但在[本案例](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/classes#type-parameter-constraints)确实表明有三种不同类型的约束,当使用这三种约束时,它们必须具体为"primary_constraint","secondary_constraints","constructor_constraint". (5认同)
  • 我很好奇......订购意义背后的原因究竟是什么? (2认同)
  • @TomWright:Damien是对的; 我没有特别的理由除了解析器作者的方便之外.如果我有我的druthers类型约束的语法将相当*更多*详细.`class`很糟糕,因为它意味着"引用类型",而不是"类".我会更喜欢像'哪里T不是结构'那样冗长的东西 (2认同)