C# 扩展方法优先级

C# 扩展方法优先级,c#,extension-methods,C#,Extension Methods,我从中了解到,永远不会调用与基类型上的现有扩展方法具有相同名称和签名的扩展方法,但是“重写”扩展方法本身又如何呢 using System; using System.Linq; using System.Collections.Generic; namespace ConsoleApplication1 { public class Program { public static void Main(string[] args) {

我从中了解到,永远不会调用与基类型上的现有扩展方法具有相同名称和签名的扩展方法,但是“重写”扩展方法本身又如何呢

using System;
using System.Linq;
using System.Collections.Generic;


namespace ConsoleApplication1
{

    public class Program
    {

        public static void Main(string[] args)
        {

            var query = (new[] { "Hans", "Birgit" }).Where(x => x == "Hans");
            foreach (var o in query) Console.WriteLine(o);
        }

    }


    public static class MyClass
    {
        public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate)
        {
            foreach (var obj in source)
                if (predicate(obj)) yield return obj;
        }
    };

}
使用系统;
使用System.Linq;
使用System.Collections.Generic;
命名空间控制台应用程序1
{
公共课程
{
公共静态void Main(字符串[]args)
{
var query=(new[]{“Hans”,“Birgit”})。其中(x=>x==“Hans”);
foreach(查询中的var o)控制台;
}
}
公共静态类MyClass
{
公共静态IEnumerable,其中(此IEnumerable源,Func谓词)
{
foreach(源中的var obj)
if(谓词(obj))产生返回obj;
}
};
}

当我调试这个程序时,我确实遇到了我自己的扩展方法,而不是System.Linq提供的扩展方法(尽管包含了名称空间)。我遗漏了什么,或者扩展方法也有优先级吗?

当编译器搜索扩展方法时,它从与调用代码位于同一名称空间的类中声明的方法开始,然后向外工作,直到到达全局名称空间。因此,如果您的代码位于命名空间
Foo.Bar.Baz
,它将首先搜索
Foo.Bar.Baz
,然后搜索
Foo.Bar
,然后搜索
Foo
,然后搜索全局命名空间。一旦找到任何符合条件的扩展方法,它将立即停止。如果在同一步骤中发现多个合格的扩展方法,并且没有一个比另一个更好(使用正常的重载规则),那么您将得到一个编译时模糊错误

然后(如果没有找到任何东西),它会考虑使用指令
导入的扩展方法。因此,如果您将扩展方法移动到与您的方法无关的另一个名称空间,您要么会由于歧义(如果您使用
using
指令导入名称空间)而得到编译时错误,要么它只会找到
System.Linq
方法(如果您没有导入包含方法的名称空间)


这在C#规范第7.6.5.2节中有详细说明。

这里是一个较长的示例,有五种可能的
,其中
方法:

using System;
using System.Collections.Generic;
using System.Linq; // **OUTSIDE**

namespace Me.MyName
{
  using System.MySuperLinq; // **INSIDE**

  static class Test
  {
    static void Main()
    {
      (new[] { "Hans", "Birgit" }).Where(x => x == "Hans");
    }
  }
}

namespace System.MySuperLinq
{
  static class Extensions
  {
    public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate)
    {
      Console.WriteLine("My own MySuperLinq method");
      return null; // will fix one day
    }
  }
}

namespace Me.MyName
{
  static class Extensions
  {
    public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate)
    {
      Console.WriteLine("My own MyName method");
      return null; // will fix one day
    }
  }
}

namespace Me
{
  static class Extensions
  {
    public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate)
    {
      Console.WriteLine("My own Me method");
      return null; // will fix one day
    }
  }
}

// this is not inside any namespace declaration
static class Extensions
{
  public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate)
  {
    Console.WriteLine("My own global-namespace method");
    return null; // will fix one day
  }
}
使用系统;
使用System.Collections.Generic;
使用System.Linq;//**外面**
名称空间Me.MyName
{
在内部使用System.MySuperLinq;//****
静态类测试
{
静态void Main()
{
(新[]{“Hans”,“Birgit”})。其中(x=>x==“Hans”);
}
}
}
命名空间System.MySuperLinq
{
静态类扩展
{
公共静态IEnumerable,其中(此IEnumerable源,Func谓词)
{
WriteLine(“我自己的MySuperLinq方法”);
return null;//将在某一天修复
}
}
}
名称空间Me.MyName
{
静态类扩展
{
公共静态IEnumerable,其中(此IEnumerable源,Func谓词)
{
WriteLine(“我自己的MyName方法”);
return null;//将在某一天修复
}
}
}
名称空间我
{
静态类扩展
{
公共静态IEnumerable,其中(此IEnumerable源,Func谓词)
{
WriteLine(“我自己的自我方法”);
return null;//将在某一天修复
}
}
}
//这不在任何命名空间声明中
静态类扩展
{
公共静态IEnumerable,其中(此IEnumerable源,Func谓词)
{
WriteLine(“我自己的全局名称空间方法”);
return null;//将在某一天修复
}
}
尝试依次删除首选的方法,以查看C#编译器使用的“优先级”

  • C#编译器将首先搜索
    Me.MyName
    名称空间中的静态类,因为这是“当前”名称空间
  • 然后它将在
    System.MySuperLinq
    中搜索,因为
    using
    在内部
  • 然后它将进入一个级别,并在
    Me
    名称空间中搜索
  • 然后它将在全局(
    null
    )命名空间中搜索
  • 最后,它将搜索
    System
    System.Collections.Generic
    System.Linq

如果在一个“级别”(上面的项目符号)上发现两个或多个同等相关的方法,这就是编译时歧义(不会编译)。

这取决于
using
指令是位于
名称空间
声明的内部还是外部。谢谢Jon,这澄清了问题。然而,我也假设当扩展有多个解决方案时,我会得到一个编译器错误(未测试,但感谢@xanatos指出这一点)。@xanatos:我无法重现。你到底是什么意思?(我尝试在全局命名空间中声明一个扩展方法,在
X
中声明一个扩展方法,然后从
X.Foo
中的类调用它;
X
中的扩展方法被调用。)@xanatos:对,这就是我在回答中包含的歧义示例,因为使用了两个不同的
指令。这与“从包含调用代码的名称空间开始并向外工作”步骤不同。Jon,当在同一名称空间中找到两个或多个扩展方法时,这也将是一个歧义。您不会使用
指令显示您的
(需要
IEnumerable
才能在范围内),或者您是否声明了
命名空间
。这可能是相关的。例如,您是否在
名称空间系统.Linq{}
中编写代码?您的
是否使用
指令来
系统。Linq
放在
名称空间的内部或外部
?@JeppeStigNielsen在上述(编辑的)代码中添加了名称空间,首先搜索名称空间
控制台应用程序1
,然后搜索全局(
null
)名称空间,然后是
系统
系统.Linq
,和
System.Collections.Generic
。由于
MyClass
位于命名空间
ConsoleApplication1
中,因此首先找到它。