【问题标题】:Extension methods and compile-time checking扩展方法和编译时检查
【发布时间】:2010-10-13 06:11:57
【问题描述】:

也许有点棘手,但我想知道为什么。在System.Linq.Enumerable.csSystem.Core.dll 中,我们有:

public static int Count<TSource>(this IEnumerable<TSource> source);

在我的代码中,我正在做一些邪恶的事情:

namespace Test
{
   public static class Extensions
   {
     public static int Count<TSource>(this IEnumerable<TSource> source)
     {
        return -1; //evil code
     }
   }

   //commented temporarily
   //public static class CommentedExtensions
   //{
   //  public static int Count<TSource>(this IEnumerable<TSource> source)
   //  {
   //     return -2; //another evil code
   //  }
   //}

   public static void Main(string[] args)
   {
     Console.WriteLine(Enumerable.Range(0,10).Count());   // -1, evil code works
     Console.Read();
   }
}

如果我取消注释CommentedExtensions,我会得到一个编译错误,如预期的那样“这个调用是模棱两可的blabla”。但是为什么我第一次没有收到这个错误?也很暧昧!

EDIT 经过另一次测试,我发现如果扩展方法在不同的命名空间中,我不会出现编译错误,即使它们完全一样。为什么是允许的?它在 c# 中带来了模棱两可的方法调用。

EDIT2 我知道实际上这两个Count 在IL 中是不同的。事实上它正在调用

Enumerable.Count(Enumerable.Range(0,10))

我邪恶的扩展方法正在调用:

MyExtension.Count(Enumerable.Range(0,10))

所以它们是不同的。但我仍然认为这是一种难闻的气味。我们有“真正的”扩展方法吗?哪些可以防止作恶?

【问题讨论】:

    标签: c# .net linq extension-methods


    【解决方案1】:

    C# language specification 的第 7.6.5.2 节描述了编译器如何确定哪些扩展方法在范围内,以及哪些扩展方法优先于其他方法:

    对 C [(一种候选扩展方法)] 的搜索过程如下:

    • 从最近的封闭命名空间声明开始,继续每个封闭命名空间声明,并以包含的编译单元结束,连续尝试找到一组候选扩展方法:
      • 如果给定的命名空间或编译单元直接包含具有合格扩展方法 Mj 的非泛型类型声明 Ci,则这些扩展方法的集合就是候选集
      • 如果使用给定命名空间或编译单元中的命名空间指令导入的命名空间直接包含具有合格扩展方法 Mj 的非泛型类型声明 Ci,则这些扩展方法的集合就是候选集。

    这意味着如果您在与调用它们的代码相同的命名空间中具有扩展方法,则会选择这些扩展方法。封闭命名空间中的扩展方法将优先于已导入的其他命名空间。

    【讨论】:

      【解决方案2】:

      C# 似乎首先在当前名称空间中查找

      在这个例子中,IL 是

      .method public hidebysig static void  Main(string[] args) cil managed
      {
        .entrypoint
        // Code size       27 (0x1b)
        .maxstack  8
        IL_0000:  nop
        IL_0001:  ldc.i4.0
        IL_0002:  ldc.i4.s   10
        IL_0004:  call       class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32,
                                                                                                                                        int32)
        IL_0009:  call       int32 Test.Extensions::Count<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)
        IL_000e:  call       void [mscorlib]System.Console::WriteLine(int32)
        IL_0013:  nop
        IL_0014:  call       int32 [mscorlib]System.Console::Read()
        IL_0019:  pop
        IL_001a:  ret
      } // end of method Program::Main
      

      如果我在这种情况下将主方法移动到另一个命名空间 (XXX),编译器会将方法解析为 System.Linq 版本

      namespace Test
      {
          public static class Extensions
          {
              public static int Count<TSource>(this IEnumerable<TSource> source)
              {
                  return -1; //evil code
              }
          }
      
      }
      
      namespace XXX{
      
          public static class Program
          {
              public static void Main(string[] args)
              {
                  Console.WriteLine(Enumerable.Range(0, 10).Count());   // -1, evil code works
                  Console.Read();
              }
         }
      }
      
      
      .method public hidebysig static void  Main(string[] args) cil managed
      {
        .entrypoint
        // Code size       27 (0x1b)
        .maxstack  8
        IL_0000:  nop
        IL_0001:  ldc.i4.0
        IL_0002:  ldc.i4.s   10
        IL_0004:  call       class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32,
                                                                                                                                        int32)
        IL_0009:  call       int32 [System.Core]System.Linq.Enumerable::Count<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)
        IL_000e:  call       void [mscorlib]System.Console::WriteLine(int32)
        IL_0013:  nop
        IL_0014:  call       int32 [mscorlib]System.Console::Read()
        IL_0019:  pop
        IL_001a:  ret
      } // end of method Program::Main
      

      要在后一个示例中显式使用您的方法,请使用

      namespace XXX{
          using Test;
          public static class Program
          {
              public static void Main(string[] args)
              {
                  Console.WriteLine(Enumerable.Range(0, 10).Count());   // -1, evil code works
                  Console.Read();
              }
      
          }
      }
      
      .method public hidebysig static void  Main(string[] args) cil managed
      {
        .entrypoint
        // Code size       27 (0x1b)
        .maxstack  8
        IL_0000:  nop
        IL_0001:  ldc.i4.0
        IL_0002:  ldc.i4.s   10
        IL_0004:  call       class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32,
                                                                                                                                        int32)
        IL_0009:  call       int32 Test.Extensions::Count<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)
        IL_000e:  call       void [mscorlib]System.Console::WriteLine(int32)
        IL_0013:  nop
        IL_0014:  call       int32 [mscorlib]System.Console::Read()
        IL_0019:  pop
        IL_001a:  ret
      } // end of method Program::Main
      

      【讨论】:

        【解决方案3】:

        如果您创建一个新类并将 usings 添加到两个命名空间,然后使用在两个命名空间中定义的方法,则错误应该再次出现。

        扩展方法是通过命名空间来区分的,而不是通过它们声明的静态类来区分的。如果两个扩展方法被定义在同一个命名空间中,编译器可以知道哪两个。

        【讨论】:

          【解决方案4】:

          我认为,您正在编写两个具有相同重载的方法,这违反了 OOP 的原则。

          如果扩展方法与您使用的方法位于相同的命名空间范围内,那么如果扩展名相同,它将优先于 System.Core.dll 方法(因为它是在使用位置找到的最接近的重载)方法存在于两个命名空间中。

          为了证明这一点,请将您的扩展方法名称更改为 MyCount1(...) & MyCount2(...) ,然后它应该适合您。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-03-01
            • 1970-01-01
            • 2019-07-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多