派生类上没有扩展方法'First'

时间:2013-05-29 16:09:05

标签: c# generics extension-methods

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

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'的定义,也没有扩展方法'First'接受类型'List2'的第一个参数(你是否缺少using指令或汇编引用?)

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

这只是一个误导性错误的情况,问题是它有两个扩展方法,并且不知道选择哪一个?

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

3 个答案:

答案 0 :(得分:7)

问题是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>();

然后你没事。

答案 1 :(得分:3)

考虑这个课程:

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();
            }
        }

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

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

答案 2 :(得分:-1)

Generic方法有什么问题?

当您的班级继承自List1IEnumerable<Class2>时,它实际上会继承3种类型:List1IEnumerable<Class1>IEnumerable<Class2>(以及{{1} })。这意味着你的类有两个不同的object实现,因为编译器不会生成不带泛型参数的“默认”方法,因为它无法弄清楚T是什么。

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

请考虑以下代码段:

First<>()

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>