2010-10-13 95 views
5

也许有点棘手,但我不知道为什么。在System.Core.dllSystem.Linq.Enumerable.cs我们:扩展方法和编译时检查

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,我会得到一个编译错误说:“这个调用暧昧布拉布拉“如预期。但为什么我第一次没有得到这个错误?它也含糊不清!

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

EDIT2我知道其实这两个Count在IL中是不同的。事实上,它的呼唤

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

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

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

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

回答

4

第7.6.5节。所述C# language specification的2描述了编译器如何确定哪一个扩展方法是在范围,并且其扩展方法优先于其它:

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

  • 与最接近的封闭命名空间声明开始,每个封闭命名空间声明继续,并与包含编译单元结束,连续尝试来找到一组候选的扩展方法:
    • 如果给定的命名空间或compilat ion单元直接包含具有合格扩展方法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 

明确地使用你的方法在后者的例子ÿ OU使用

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 
0

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

扩展方法被命名空间区分开,而不是他们在声明的静态类,编译器可以知道,如果在相同的命名空间定义了两个扩展方法其中一二走。

0

我想,你正在写两种方法,它们有相同的过载,这与OOP的原理相违背。

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

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