【问题标题】:Extension method priority扩展方法优先级
【发布时间】:2015-02-24 14:49:33
【问题描述】:

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

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 提供的扩展方法(尽管包括命名空间)。我是否遗漏了什么,或者扩展方法是否也有优先级?

【问题讨论】:

  • 您没有显示您的using 指令(IEnumerable&lt;&gt; 需要在范围内),或者您是否声明了namespace。这可能是相关的。例如,您是否在namespace System.Linq { } 中编写代码?你的using 指令是放在System.Linq 内部还是外部namespace
  • @JeppeStigNielsen 添加了命名空间
  • 在上面(编辑的)代码中,首先搜索命名空间ConsoleApplication1,然后是全局(null)命名空间,然后是SystemSystem.LinqSystem.Collections.Generic。由于MyClass在命名空间ConsoleApplication1中,所以先找到。

标签: c# extension-methods


【解决方案1】:

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

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

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

【讨论】:

  • 这取决于using 指令是位于namespace 声明的内部还是外部。
  • 感谢 Jon,这解决了问题。但是,我还假设当有多个扩展解决方案时,我会遇到编译器错误(未经测试,但感谢@xanatos 指出)。
  • @xanatos:我无法重现。你到底什么意思? (我尝试在全局命名空间中声明一个扩展方法,在X 中声明一个,然后从X.Foo 中的一个类中调用它;X 中的一个被调用。)
  • @xanatos:是的,这是我在回答中包含的模棱两可的例子,因为有两个不同的using 指令。这与“从包含调用代码的命名空间开始并向外工作”步骤相同。
  • Jon,当在同一个命名空间中发现两个或多个扩展方法时也会产生歧义。
【解决方案2】:

这是一个较长的示例,其中包含五种可能的 Where&lt;&gt; 方法:

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# 编译器使用的“优先级”。

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-07-28
    • 1970-01-01
    • 2012-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多