无扩展方法'First'派生类

本文关键字:派生 First 扩展 方法 | 更新日期: 2023-09-27 18:15:59

给出(非常简单的)代码。

public class Class1 
{
}
public class Class2 : Class1
{
}
public class List1 : System.Collections.Generic.IEnumerable<Class1>
{
    public new System.Collections.Generic.IEnumerator<Class1> GetEnumerator()
    {
        yield return new Class1();            
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

public class List2  : List1 , System.Collections.Generic.IEnumerable<Class2> 
{       
    public new System.Collections.Generic.IEnumerator<Class2> GetEnumerator()
    {
        yield return new Class2();              
    }  
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

然后代码

var l = new List2();
var first = l.First();

将不编译,但给出错误

'List2'不包含'First'的定义,并且没有找到接受'List2'类型的第一个参数的扩展方法'First'(您是否缺少using指令或程序集引用?)

如果List2不是从List1派生的,那么它可以编译,这证明List2确实有一个有效的扩展方法。

这是一个简单的误导错误的情况下,问题是它有两个多扩展方法,不知道哪一个选择?

如果是,为什么它不能告诉Class2是更具体的版本,就像编译器使用方法重载解析一样?

无扩展方法'First'派生类

问题是List2实现了IEnumerable<Class1>IEnumerable<Class2>,所以编译器不知道要调用Enumerable.First<Class1>Enumerable.First<Class2>中的哪一个。实际上,编译器找不到最好的T来调用Enumerable.First<T>(l),因此它不会将任何通用的Enumerable.First<T>算作合格的方法。因此,它现在只寻找一个名为First的非泛型方法,该方法只有一个参数,l可以隐式转换为该参数,但它找不到任何参数。

你可以明确地说

var first = l.First<Class1>();

var first = l.First<Class2>();

考虑这个类:

public class List1 : IEnumerable<string>, IEnumerable<object>
        {
            IEnumerator<object> IEnumerable<object>.GetEnumerator()
            {
                return GetEnumerator();
            }
            public IEnumerator<string> GetEnumerator()
            {
                throw new NotImplementedException();
            }
            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }
        }

so编译器不知道调用IEnumerable<string>IEnumerable<object>中哪个的First()方法。这个例子是你的情况的简化版本。

假设你在一个类上写了一个扩展方法,而你没有它的源代码,但是在下一个版本中,他们在那个类上实现了完全相同的方法。在这种情况下,您的旧代码不再可靠,因此最好的解决方案是编译时错误。

泛型方法的问题是什么?

当你的类从List1IEnumerable<Class2>继承时,它实际上继承了3种类型:List1, IEnumerable<Class1>IEnumerable<Class2>(和object)。这意味着你的类有2种不同的First<>()实现,因为编译器不会生成"默认"方法,不接受泛型参数,因为它不能弄清楚T是什么。

这并不意味着First<>()不可用,它只是意味着你必须指定T是什么。

考虑下面的代码片段:
        var l2 = new List2();
        l2.First<Class1>();
        l2.First<Class2>();

First<Class1>()First<Class2>()都是可用的,因为编译器无法确定T是什么。

但是在这个代码片段中:

l2.First((Class2 c) => c.ToString() == "");

编译器可以算出TClass2,所以下面的代码可以正常编译。

良好的设计实践

尽管前面的方法是有效的,但是让一个类两次继承同一个接口并不是一个好的设计实践。(在您的例子中,一次通过Class1,第二次通过IEnumerable<Class2>的显式继承)。为了更好的设计,你可以实现一个通用的抽象类,并使你的列表从这个类派生。

public abstract class CommonListBase
{
}
public class List1 : CommonListBase, IEnumerable<Class1>
{
    public System.Collections.Generic.IEnumerator<Class1> GetEnumerator()
    {
        yield return new Class1();
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

public class List2 : CommonListBase, IEnumerable<Class2>
{
    public System.Collections.Generic.IEnumerator<Class2> GetEnumerator()
    {
        yield return new Class2();
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}