2010-10-13 8 views
5

Tal vez un poco complicado, pero me pregunto por qué. En System.Linq.Enumerable.cs de System.Core.dll tenemos:Métodos de extensión y verificación en tiempo de compilación

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

en mi código que estoy haciendo algo mal:

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

Si Descomentar CommentedExtensions, voy a conseguir un error de compilación diciendo "esta llamada es ambigua blabla " como se esperaba. ¿Pero por qué no obtuve este error la primera vez? ¡También es ambiguo!

EDITAR Después de otra prueba, me di cuenta que no vamos a obtener errores de compilación si los métodos de extensión están en diferentes espacios de nombres, incluso ellos son totalmente iguales. ¿Por qué está permitido? Trae una llamada ambigua de métodos en C#.

Edit2 sé, de hecho, los dos son diferentes en Count IL. De hecho, se llama

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

y mi método de extensión del mal está llamando:

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

por lo que son diferentes. Pero aún creo que es un mal olor. ¿Tenemos métodos de extensión "reales"? que puede prevenir el mal comportamiento?

Respuesta

4

Sección 7.6.5.2 de la C# language specification describe cómo el compilador determina que los métodos de extensión son en su alcance, y que los métodos de extensión tienen precedencia sobre otros:

La búsqueda de C [(un método de extensión candidato)] procede como sigue:

  • a partir de la declaración de espacio de nombres que encierra más cercano, continuando con cada declaración de espacio de nombres que encierra, y terminando con la unidad de compilación que contiene, los sucesivos intentos se hacen para encontrar un candidato conjunto de métodos de extensión:
    • Si el espacio de nombres o compilat dada ion unit contiene directamente declaraciones de tipo no genéricas Ci con métodos de extensión elegibles Mj, entonces el conjunto de esos métodos de extensión es el conjunto candidato
    • Si los espacios de nombres importados mediante el uso de directivas de espacios de nombres en el espacio de nombres o la unidad de compilación contienen directamente un tipo no genérico declaraciones Ci con métodos de extensión elegibles Mj, entonces el conjunto de esos métodos de extensión es el conjunto candidato.

Lo que significa que si usted tiene los métodos de extensión en el mismo espacio de nombres que el código de la que usted los llama, se seleccionan estos métodos de extensión. Los métodos de extensión en un espacio de nombre delimitador se elegirán sobre otros espacios de nombres que se hayan importado.

2

Parece que el C# mira en el espacio de nombres actual primer

En este ejemplo, la IL es

.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 

Si muevo el método principal en el otro espacio de nombres (XXX), en este caso el compilador resuelve el método para la versión 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 

para utilizar explícitamente el método en el último ejemplo y ou utilizar

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

Si crea una nueva clase y añadir usings a ambos espacios de nombres y luego utilizar el método que se define en los dos espacios de nombres, el error debería estar allí de nuevo.

Los métodos de extensión se distinguen por espacios de nombres, no por las clases estáticas en las que están declarados. El compilador puede saber cuál dos tomar si se definen dos métodos de extensión en el mismo espacio de nombres.

0

Creo que está escribiendo Dos métodos con la misma sobrecarga, lo que va en contra de los principios de OOP.

Si el método de extensión está dentro del mismo ámbito de espacio de nombre que el de su uso, tendrá prioridad (ya que es la sobrecarga más cercana encontrada en el lugar de uso) sobre el System.Core.dll, si es el mismo los métodos de extensión existen en ambos espacios de nombres.

Para probar esto, cambie el nombre de su método de extensión a MyCount1 (...) & MyCount2 (...), entonces debería funcionar para usted.

Cuestiones relacionadas