2010-10-13 5 views
5

Возможно, немного сложно, но мне интересно, почему. В 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, я получаю ошибку компиляции, говоря «это вызов неоднозначен блаб " как и ожидалось. Но почему я впервые не получил эту ошибку? Это также неоднозначно!

EDIT После еще одного теста я обнаружил, что не буду получать ошибки компиляции, если методы расширения находятся в разных пространствах имен, даже если они полностью совпадают. Почему это разрешено? Это вызывает неоднозначный вызов методов в C#.

EDIT2 Я знаю, что на самом деле два Count отличаются друг от друга. На самом деле это звонит

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

и мой злой метод расширения звонит:

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

так они разные. Но все же я думаю, что это плохой запах. Есть ли у нас «реальные» методы расширения? который может предотвратить злобное поведение?

ответ

4

Раздел 7.6.5.2 C# language specification описывает, как компилятор определяет, какие методы расширения находятся в области видимости, а какие методы расширения имеют приоритет над другими:

Поиски C [(метод расширения кандидата)] протекают следующим образом:

  • Начиная с ближайшей декларации вшита пространства имен, продолжая с каждым вмещающего пространства имен, и заканчивая содержащей единицы компиляции, последовательные попытки найти кандидата набор методов расширения:
    • Если данное пространство имен или compilat ионный блок непосредственно содержит декларации типа общего типа Ci с подходящими методами расширения Mj, тогда набор этих методов расширения является набором кандидатов
    • Если пространства имен, импортированные с использованием директив пространств имен в данном пространстве имен или в единицу компиляции, непосредственно содержат не общий тип объявления Ci с подходящими методами расширения Mj, то набор этих методов расширения является набором кандидатов.

Это означает, что если у вас есть методы расширения в том же пространстве имен, чем код, из которого вы называете их, выбирают эти методы расширения. Методы расширения в охватывающем пространстве имен будут выбраны по другим импортированным пространствам имен.

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 
0

Если создать новый класс и добавить usings в обоих пространствах имен, а затем использовать метод, который определен в обоих пространствах имен, ошибка должна быть там снова.

Методы расширения различаются пространствами имен, а не статическими классами, в которых они объявлены. Компилятор может знать, какой из них взять, если два метода расширения определены в одном пространстве имен.

0

Я думаю, вы пишете два метода с той же перегрузкой, которая противоречит принципам ООП.

Если метод расширения находится в пределах той же области пространства имен, что и область использования, то она будет иметь приоритет (поскольку это ближайшая перегрузка, найденная в месте использования) через файл System.Core.dll, если тот же методы расширения существуют в обоих пространствах имен.

Чтобы это доказать, измените имя метода расширения на MyCount1 (...) & MyCount2 (...), то он должен работать на вас.

Смежные вопросы