2010-10-13 12 views
5

Vielleicht ein wenig schwierig, aber ich frage mich warum. In System.Linq.Enumerable.cs von System.Core.dll haben wir:Erweiterungsmethoden und Kompilierzeitüberprüfung

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

In meinem Code mache ich etwas Böses:

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(); 
    } 
} 

Wenn ich CommentedExtensions Kommentar-, werde ich einen Compiler-Fehler erhalten sagen: „Dieser Aufruf mehrdeutig blabla ist " wie erwartet. Aber warum habe ich diesen Fehler nicht gleich beim ersten Mal bekommen? Es ist auch mehrdeutig!

BEARBEITEN Nach einem anderen Test habe ich festgestellt, dass ich keine Kompilierfehler bekomme, wenn die Erweiterungsmethoden in verschiedenen Namespaces sind, auch wenn sie vollständig identisch sind. Warum ist es erlaubt? Es bringt mehrdeutigen Aufruf von Methoden in C#.

EDIT2 Ich weiß in der Tat die beiden Count sind in IL unterschiedlich. In der Tat ruft es

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

und meine bösen Extension-Methode ruft:

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

so sie anders sind. Aber trotzdem denke ich, dass es ein schlechter Geruch ist. Haben wir "echte" Erweiterungsmethoden? was kann das böse Verhalten verhindern?

Antwort

4

Abschnitt 7.6.5.2 des C# language specification beschreibt, wie der Compiler, der Erweiterungsmethoden sind im Rahmen bestimmt und die Erweiterungsmethoden haben Vorrang vor den anderen:

Die Suche C [(a Kandidatenerweiterungsmethode)] wie folgt vorgegangen:

  • Beginnend mit der nächsten einschließenden Namespace-Deklaration, mit jeder umschließenden Namensraum-Deklaration fortgesetzt und mit der enthaltenden Kompilationseinheit endet, werden aufeinanderfolgende Versuche unternommen, eine Kandidatensatz von Erweiterungsmethoden zu finden:
    • Wenn der angegebene Namespace oder Compilat ion unit enthält direkt nicht-generische Typdeklarationen Ci mit den zulässigen Erweiterungsmethoden Mj, dann ist der Satz dieser Erweiterungsmethoden der Kandidatensatz
    • Wenn Namespaces, die mithilfe von Namespace-Direktiven im angegebenen Namespace oder in der Kompilierungseinheit importiert werden, direkt einen nicht-generischen Typ enthalten Deklarationen Ci mit geeigneten Erweiterungsmethoden Mj, dann ist der Satz dieser Erweiterungsmethoden der Kandidatensatz.

Was bedeutet, dass, wenn Sie Erweiterungsmethoden im gleichen Namensraum als der Code, aus dem Sie sie nennen haben, werden diese Erweiterungsmethoden ausgewählt. Erweiterungsmethoden in einem einschließenden Namespace werden über andere Namespaces ausgewählt, die importiert wurden.

2

Es erscheint der C# Namensraum in Strom IL ist

.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 

Wenn ich die Hauptmethode in den anderen Namensraum (XXX) in diesem Fall bewegt sich die in diesem Beispiel

zuerst sieht Compiler löst die Methode auf die System.Linq Version

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 

um explizit Ihre Methode in dem letzteren Beispiel y verwenden ou verwenden

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

Wenn Sie eine neue Klasse erstellen, und fügen Sie usings beide Namespaces und dann Ihre Methode verwenden, die in den beiden Namensräume definiert ist, sollte der Fehler wieder dabei sein.

Erweiterungsmethoden unterscheiden sich durch Namespaces, nicht durch die statischen Klassen, in denen sie deklariert sind. Der Compiler kann wissen, welche zwei verwendet werden, wenn zwei Erweiterungsmethoden im selben Namespace definiert sind.

0

Ich denke, Sie schreiben zwei Methoden mit der gleichen Überladung, die gegen die Prinzipien der OOPs ist.

Wenn die Erweiterungsmethode im selben Namespace-Bereich liegt wie Ihre Verwendung, hat sie Vorrang vor der System.Core.dll (wenn es die gleiche ist) Erweiterungsmethoden gibt es in beiden Namespaces.

Um dies zu beweisen, ändern Sie den Namen Ihrer Erweiterungsmethode zu MyCount1 (...) & MyCount2 (...), dann sollte es für Sie arbeiten.

Verwandte Themen