【问题标题】:No extension method 'First' on derived class派生类上没有扩展方法“First”
【发布时间】:2013-05-29 16:09:05
【问题描述】:

鉴于(非常简单的)代码。

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 是更具体的版本,就像编译器使用方法重载解析一样?

【问题讨论】:

  • 你当然有using System.Linq;,对吧?
  • 是的。 (否则当 List2 不是从 List1 派生时它不会编译)。

标签: c# generics extension-methods


【解决方案1】:

问题在于List2 同时实现了IEnumerable&lt;Class1&gt;IEnumerable&lt;Class2&gt;,因此编译器不知道要调用Enumerable.First&lt;Class1&gt;Enumerable.First&lt;Class2&gt; 中的哪一个。实际上,编译器没有找到最好的T 来调用Enumerable.First&lt;T&gt;(l),因此它不会将任何泛型Enumerable.First&lt;T&gt; 视为合格方法。因此,它现在只寻找一个名为 First 的非泛型方法,它带有一个可以隐式转换为 l 的参数,但它找不到任何参数。

你可以直言不讳

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

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

然后你就没事了。

【讨论】:

  • 这两个调用有什么区别,你期望 first.GetType() 在这两种情况下返回什么?
【解决方案2】:

考虑这个类:

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&lt;string&gt;IEnumerable&lt;object&gt; 中的哪一个的 First() 方法。此示例是您的情况的简化版本。

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

【讨论】:

    【解决方案3】:

    泛型方法有什么问题?

    当您的类从List1IEnumerable&lt;Class2&gt; 继承时,它实际上是从3 种类型继承的:List1IEnumerable&lt;Class1&gt;IEnumerable&lt;Class2&gt;(和object)。这意味着您的类对 First&lt;&gt;() 有 2 种不同的实现,因为编译器不会生成不接受泛型参数的“默认”方法,因为它无法确定 T 是什么。

    这并不意味着First&lt;&gt;() 不可用,只是意味着您必须指定 T 是什么。

    考虑以下代码sn-p:

            var l2 = new List2();
    
            l2.First<Class1>();
            l2.First<Class2>();
    

    First&lt;Class1&gt;()First&lt;Class2&gt;() 都可用,从那时起编译器就无法确定 T 是什么。

    但是在这段代码中sn-p:

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

    编译器可以计算出TClass2,所以下面的编译就可以了。

    良好的设计实践

    尽管前面的方法是有效的,但让一个类两次继承同一个接口并不是一个好的设计实践。 (在你的情况下,一次通过Class1,第二次通过IEnumerable&lt;Class2&gt; 的显式继承)。为了更好的设计,您可以实现一个通用的抽象类,并使您的列表派生自此类。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-04-09
      • 1970-01-01
      • 2021-11-19
      • 2011-07-22
      • 2019-05-16
      • 2013-04-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多