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

Jai*_*Lee 21 c# generics

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
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

我有一段代码.但它总是失败.

如果我换Update(new List<T>() { entity })Update((new List<T>() { entity }).AsEnumerable()),那就没问题了.

删除第三种方法时也可以Update<TSub>(TSub entity) where TSub : T.

谁能告诉我为什么?

Eri*_*ert 21

好的,让我们仔细看看.我们有

Update(new List<T>()); 
Run Code Online (Sandbox Code Playgroud)

还有三位候选人 - 请注意我们只关心这些候选人的签名,因此我们将删除不属于签名的返回类型和约束:

Update(IEnumerable<T> entities)
Update<U>(U entity) 
Update<V>(IEnumerable<V> entities) 
Run Code Online (Sandbox Code Playgroud)

我们的首要任务是对最后两位候选人进行类型推断.如果推理失败,则他们不适用候选人.

考虑第二种方法

Update<U>(U entity) 
Run Code Online (Sandbox Code Playgroud)

我们有一个类型List<T>和形式参数的参数U.因此,我们推断UList<T>.

考虑第三种方法:

Update<V>(IEnumerable<V> entities)
Run Code Online (Sandbox Code Playgroud)

我们有一个类型的参数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) 
Run Code Online (Sandbox Code Playgroud)

所有这些候选人都适用吗?是.在每种情况下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) 
Run Code Online (Sandbox Code Playgroud)

我们有一个类型的参数IEnumerable<T>,所以我们推断第二个和第三个是:

Update(IEnumerable<T> entities)
Update<IEnumerable<T>>(IEnumerable<T> entity) 
Update<T>(IEnumerable<T> entities) 
Run Code Online (Sandbox Code Playgroud)

现在我们有三个具有相同参数列表的适用候选者.在那里建造的那些自然比自然更差,所以我们消除第二个和第三个,只留下第一个.它赢了,它没有任何违反的限制.

删除第三种方法时也可以

你的陈述是假的; 这将产生与第一个场景相同的错误.拿走第三个候选人不会导致第一个候选人突然开始击败第二个候选人.


And*_*rew 12

约束不是签名的一部分,Eric Lippert有一篇关于这个主题的精彩文章.


Pie*_*ens 2

List<T>您本质上是在问为什么编译器不创建从to的隐式转换IEnumerable<T>。原因是 C# 团队做出了一个深思熟虑的设计决定,即潜在歧义的情况必须由程序员而不是编译器来解决。(请注意,VB.NET 团队做出了不同的决定,始终尝试与感知到的程序员意图一致的明智做法。)

这种情况的优点是可以最大限度地减少意外——在幕后不会发生任何意外的事情;缺点是偶尔需要更详细的代码。