2010-10-13 51 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(...),那麼它應該適合您。