扩展方法和编译时检查

时间:2010-10-13 06:11:57

标签: c# .net linq extension-methods

也许有点棘手,但我想知道为什么。在System.Linq.Enumerable.cs System.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”,如预期的那样。但为什么我第一次没有得到这个错误?这也很暧昧!

编辑经过另一次测试后,我发现如果扩展方法位于不同的命名空间中,即使它们完全相同,我也不会遇到编译错误。为什么允许这样做?它在c#中带来了模糊的方法调用。

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

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

我的邪恶扩展方法是调用:

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

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

4 个答案:

答案 0 :(得分:4)

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

  

搜索C [(候选扩展方法)]如下:

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

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

答案 1 :(得分: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

如果我将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

答案 2 :(得分:0)

如果您创建一个新类并向两个名称空间添加使用,然后使用在两个名称空间中定义的方法,那么错误应该再次存在。

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

答案 3 :(得分:0)

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

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

要证明这一点,请将您的扩展程序名称更改为MyCount1(...)&amp; MyCount2(...),然后它应该适合你。