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


115

我不明白为什么下面的C#代码无法编译。

如您所见,我有一个带有IEnumerable<T>参数的静态泛型方法Something (并且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)
    {
    }
}

错误我Something2(bar)排队:

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



12
您不限T于引用类型。如果使用该条件,where T: class, IA则它应该起作用。链接的答案具有更多详细信息。
德克

2
@Dirk我不认为这应该标记为重复。虽然在概念上面对值类型确实是协方差/矛盾的问题,但这里的具体情况是“此错误消息表示什么”,而作者没有意识到仅仅包括“类”就解决了他的问题。我相信将来的用户会搜索此错误消息,找到这篇文章,并感到高兴。(就像我经常做的那样)
Reginald Blue

您也可以通过直接说出来来重现这种情况Something2(foo);。不需要四处.ToList()获取List<T>T是您的泛型方法声明的类型参数)来理解这一点(a List<T>是一个IEnumerable<T>)。
杰普·斯蒂格·尼尔森

@ReginaldBlue 100%,打算发布相同的内容。类似的答案不能重复提问。
UuDdLrLrSs '11年

Answers:


218

错误消息提供的信息不足,这是我的错。对于那个很抱歉。

您遇到的问题是协方差仅适用于引用类型这一事实的结果。

您现在可能会说“但是IA是引用类型”。是的。但是你没有说T 等于 IA。您说的T是一种实现 的类型IA,而值类型可以实现一个接口。因此,我们不知道协方差是否会起作用,我们不允许这样做。

如果要使用协方差,则必须告诉编译器,类型参数是具有class约束以及IA接口约束的引用类型。

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


3
为什么你说这是你的错?
user4951

77
@ user4951:因为我实现了所有转换检查逻辑,包括错误消息。
埃里克·利珀特

@BurnsBA这仅是因果关系上的“错误”,从技术上讲,实现以及错误消息都是完全正确的。(只是不可转换的错误陈述可以详细说明其实际原因。但是,使用泛型产生良好的错误很难-与几年前的C ++模板错误消息相比,这是很清楚和简洁的。)
彼得-恢复莫妮卡

3
@ PeterA.Schneider:非常感谢。但是,我在罗斯林(Roslyn)中设计错误报告逻辑的主要目标之一是,不仅要捕捉违反的规则,而且要尽可能找出“根本原因”。例如,错误消息应该用于customers.Select(c=>c.FristName)什么?C#规范非常清楚这是一个重载解析错误:名为Select的适用方法集可以使lambda为空。但是根本原因是FirstName有错别字。
埃里克·利珀特

3
@ PeterA.Schneider:我做了很多工作,以确保涉及泛型类型推断和lambda的场景使用适当的试探法来推断出哪种错误消息最可能对开发人员有所帮助。但是我在转换错误消息方面做得差很多,尤其是在涉及差异的地方。我一直很后悔。
埃里克·利珀特

26

我只是想用一个代码示例来补充Eric的出色内幕答案,以解决可能不太熟悉通用约束的示例。

更改Something的签名如下:class约束必须首先出现

public static IList<T> Something<T>(IEnumerable<T> foo) where T : class, IA

2
我很好奇...订单意义的背后究竟是什么原因?
汤姆·赖特

5
@TomWright-规范当然不包含许多“为什么”的答案。问题,但是在这种情况下确实清楚地表明存在三种截然不同的约束类型,并且当同时使用这三种约束时,必须特别指出primary_constraint ',' secondary_constraints ',' constructor_constraint
Damien_The_Unbeliever

2
@TomWright:达米安是正确的;除了解析器作者的方便之外,我没有其他特别的原因。如果我有德鲁特话,则类型约束的语法会更加冗长。class不好,因为它表示“引用类型”,而不是“类”。我本来会更喜欢一些冗长的东西where T is not struct
Eric Lippert
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.