无扩展方法'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是更具体的版本,就像编译器使用方法重载解析一样?
问题是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()方法。这个例子是你的情况的简化版本。
假设你在一个类上写了一个扩展方法,而你没有它的源代码,但是在下一个版本中,他们在那个类上实现了完全相同的方法。在这种情况下,您的旧代码不再可靠,因此最好的解决方案是编译时错误。
泛型方法的问题是什么?
当你的类从List1
和IEnumerable<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() == "");
编译器可以算出T
是Class2
,所以下面的代码可以正常编译。
良好的设计实践
尽管前面的方法是有效的,但是让一个类两次继承同一个接口并不是一个好的设计实践。(在您的例子中,一次通过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();
}
}