扩展方法优先级

Extension method priority

我从 https://msdn.microsoft.com/en-us/library/vstudio/bb383977.aspx 了解到,与基类型上现有方法具有相同名称和签名的扩展方法永远不会被调用,但是 "overriding" 扩展方法本身呢:

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 提供的方法(尽管包含命名空间)。我错过了什么或者扩展方法也有优先级吗?

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

Then(如果没有找到任何东西)它会考虑由 using 指令导入的扩展方法。所以,如果你将你的扩展方法移动到一个与你的无关的不同命名空间,你要么会因为歧义得到一个编译时错误(如果你使用 using 指令导入命名空间)或者它只会找到 System.Linq 方法(如果您没有导入包含您的方法的命名空间)。

这在 C# 规范的第 7.6.5.2 节中指定。

这里有一个更长的例子,有五种可能的 Where<> 方法:

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
  }
}

尝试连续删除首选方法,以查看 C# 编译器使用的 "priority"。

  • C# 编译器将首先在 Me.MyName 命名空间内搜索静态 类,因为那是 "current" 命名空间。
  • 然后它将在 System.MySuperLinq 中搜索,因为 using 在里面。
  • 然后它会跳出一级并在Me 命名空间
  • 中搜索
  • 然后它将在全局 (null) 命名空间中搜索。
  • 最后,它将搜索 SystemSystem.Collections.GenericSystem.Linq

如果在一个 "level"(上面的项目符号)中找到两个或更多个同等相关的方法,那就是编译时歧义(不会编译)。