没有从 'System.Collections.Generic.List<T>' 到 'T' 的隐式引用转换。

21
class Class1<T>
{
    public virtual void Update(T entity)
    {
        Update(new List<T>() { entity }); //It's failed
    }

    public virtual void Update(IEnumerable<T> entities)
    {
    }

    public virtual void Update<TSub>(TSub entity) where TSub : T
    {
    }

    public virtual void Update<TSub>(IEnumerable<TSub> entities) where TSub : T
    {
    }
}

我有一段代码,但总是失败。

如果我用Update((new List<T>() { entity }).AsEnumerable())替换Update(new List<T>() { entity }),它就可以正常工作。

当你删除第三个方法Update<TSub>(TSub entity) where TSub : T也可以正常工作。

有人能告诉我为什么吗?


7
你进入了C#规范的黑暗部分——重载决策。加上泛型、params、继承、多态、泛型约束、dynamic、可选参数,让我心跳加速。好问题加一分。http://msdn.microsoft.com/en-us/library/aa691336(v=vs.71).aspx - Ilya Ivanov
3个回答

20

好的,让我们仔细看一下。我们有:

Update(new List<T>()); 

现在有三个候选人 -- 请注意我们只关心这些候选人的签名,因此我们将去掉返回类型和约束条件,因为它们不是签名的一部分

Update(IEnumerable<T> entities)
Update<U>(U entity) 
Update<V>(IEnumerable<V> entities) 

我们的第一个任务是对最后两个候选项进行类型推断。如果推断失败,则它们不适用于此情况。
考虑第二种方法。
Update<U>(U entity) 

我们有一个类型为List<T>的参数和一个形式参数U。因此,我们推断出UList<T>
考虑第三个方法:
Update<V>(IEnumerable<V> entities)

我们有一个类型为 List<T> 的参数和一个类型为 IEnumerable<V> 的形式参数。由于 List<T> 实现了 IEnumerable<T>,因此我们可以推断出 V 是 T。

好的,所以我们的候选列表现在包括:

Update(IEnumerable<T> entities)
Update<List<T>>(List<T> entity) 
Update<T>(IEnumerable<T> entities) 

这些候选项都是“适用的”吗?是的。在每种情况下,List<T>可转换为形式参数类型。我们暂时不能排除任何一个。

现在我们只有适用的候选项,我们必须确定哪一个是“唯一最佳”的。

我们可以立即排除第三个。第三个和第一个在形式参数列表中是相同的。C#的规则是,当你有两个方法,在它们的形式参数列表中是相同的,并且其中一个是“自然”到达的,而另一个是通过类型替换到达的,那么替换的那个失败。

我们也可以排除第一个。显然第二个中的精确匹配比第一个中的不精确匹配更好。

这样留下第二个作为最后的胜者。它赢得了重载决策战。然后在最终验证期间,我们发现违反了约束:List<T>不能保证是T的派生类。

因此,重载解析失败。你的参数导致选择的最佳方法无效。

如果我调用Update((new List<T>() { entity }).AsEnumerable()),它就会没问题。

正确。再次进行检查。三个候选项:

Update(IEnumerable<T> entities)
Update<U>(U entity) 
Update<V>(IEnumerable<V> entities) 

我们有一个类型为 IEnumerable<T> 的参数,因此我们推断第二个和第三个参数应该是:
Update(IEnumerable<T> entities)
Update<IEnumerable<T>>(IEnumerable<T> entity) 
Update<T>(IEnumerable<T> entities) 

现在我们有三个参数列表相同的可行候选者。那些在构建过程中出现的候选者自动比自然产生的候选者差,因此我们排除第二和第三个,只留下第一个。它获胜了,并且没有被违反的约束。

如果你删除第三种方法,也会没问题

你的陈述是错误的;这将产生与第一种情况相同的错误。去掉第三个候选者不会导致第一个候选者突然开始击败第二个候选者。

我仍然不太明白编译器为什么需要“猜测”我们是否允许约束成为方法签名的一部分。编译器会检查第三个方法(Update<TSub>(...)),然后推断泛型类型为List<T>,然后发现List<T>不是T,然后排除第三个方法。 - Mouhong Lin
@MouhongLin:请看Andrew答案中提到的文章。我已经尝试解释了十几次为什么将约束条件作为签名的一部分会使重载决策结果变得更糟,并且几乎没有人同意我的观点。 - Eric Lippert
@eric-lippert,感谢您的回复。现在我明白了很多。但是我还有一个问题。“当你删除第三个方法时,也会没问题。” 当我确切地删除第三个方法时,我不会收到错误信息。您能否告诉我原因? - Jailu Lee
@JailuLee:发布一个演示行为的小程序,这样我们就知道我们在谈论同一件事情。 - Eric Lippert

12

约束条件不是签名的一部分,Eric Lippert在这个主题上有一篇很棒的文章


2
你实际上想知道为什么编译器没有从 List<T> 隐式转换为 IEnumerable<T>。原因是C#团队做出了一个明确的设计决策,潜在歧义的情况必须由程序员解决,而不是由编译器解决。(请注意,VB.NET团队做出了不同的决策,始终尝试一些合理的事情,这与感知到的程序员意图一致。)
在这种情况下的优点是最大限度地减少了惊喜 - 在幕后不会发生任何意外的事情;缺点是偶尔需要更冗长的代码。

1
这实际上是从 List<T>IEnumerable<TSub> 的转换。 - Rik

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