2009-06-19 28 views
28

Los métodos de extensión se pueden asignar a los delegados que responden a su uso en un objeto, como esto:Los métodos de extensión definidos en los tipos de valores no se pueden usar para crear delegados. ¿Por qué no?

static class FunnyExtension { 
    public static string Double(this string str) { return str + str; } 
    public static int Double(this int num) { return num + num; } 
} 


Func<string> aaMaker = "a".Double; 
Func<string, string> doubler = FunnyExtension.Double; 

Console.WriteLine(aaMaker());  //Prints "aa" 
Console.WriteLine(doubler("b")); //Prints "bb" 

Si el tipo que están extendiendo es un tipo de valor, no va a funcionar:

Func<int> eightMaker = 4.Double; //Error CS1113: Extension methods 'FunnyExtension.Double(int)' defined on value type 'int' cannot be used to create delegates 
Func<int, int> intDoubler = FunnyExtension.Double; //Works 

Esto da

CS1113 Error: Los métodos de extensión 'FunnyExtension.Double (int)' define en tipo de valor 'int' no puede ser usado para crear delegados.

¿Por qué no?

+0

¿Estás seguro de que no es CS1113? –

+0

Es; fijo. Gracias – SLaks

Respuesta

18

En respuesta a mi otra respuesta, Eric Smith señala correctamente:

"... porque requeriría implícitamente boxeo el parámetro de tipo receptor ...". Que es lo que sucede de todos modos, si haces algo como esto: Func f = 5.ToString; Lo cual es perfectamente legal.

Pensar en esto me ha llevado a una nueva respuesta. Pruebe esto para el tamaño:

Los métodos ordinarios de "instancia" en las estructuras toman, en el nivel CIL, un "puntero administrado" (tipo &) como un parámetro de receptor. Esto es necesario para que los métodos de instancia en las estructuras puedan asignar a los campos de la estructura. Ver Partition II, Section 13.3.

De forma similar, los métodos de instancia en clases toman una "referencia de objeto" (tipo O) como un parámetro de receptor (la diferencia es que este es un puntero al montón administrado, y necesita ser rastreado para GC).

Dado que tanto CIL & como O se pueden implementar (y se implementan) mediante punteros, todo es genial para la implementación del delegado. Independientemente de si un delegado captura un método estático, un método de instancia de clase o un método de instancia de estructura, todo lo que necesita hacer es pasar el puntero a su _target al primer argumento de la función.

Pero el escenario que estamos discutiendo arruina eso. Un método de extensión estático tomando un int como primer argumento requiere un argumento CIL de tipo int32 (ver Partición III, sección 1.1.1). Aquí es donde las cosas se descarrilan. No veo ninguna razón por la cual no sería posible para la implementación de delegados para darse cuenta de que esto estaba sucediendo (por ejemplo, inspeccionando los metadatos asociados con el MethodInfo que se está capturando) y emitir un procesador que unbox el _target y lo pasa como el primer argumento, pero esto no es necesario para los delegados a métodos de instancia clásicos en structs, ya que esperan un puntero de todos modos y no aparece (a juzgar por el ejemplo en mi respuesta incorrecta anterior) a ser implementado. Obviamente, el tipo de valor específico en cuestión controlaría la naturaleza exacta del procesador requerido.

A menos que me falta un obstáculo más fundamental para la implementación (podría imaginar que podría plantear problemas para el verificador, por ejemplo), parece que se podría hacer un caso razonable para extender el tiempo de ejecución para apoyar este caso, pero todos los signos apuntan a que esto es una limitación del tiempo de ejecución y no del compilador C# per se.

+5

Doug, tu el análisis es excelente. Como comentamos por correo electrónico, mi colega Sreekar ha tomado algunas notas en su blog http://blogs.msdn.com/sreekarc/archive/2009/06/25/why-can-t-extension-methods-on-value- type-be-curried.aspx y he puesto algunas notas sobre cómo aparece este problema con "currying de reflexión" en mi blog. http://blogs.msdn.com/ericlippert/archive/2009/06/25/mmm-curry.aspx –

+1

¡Muchas gracias a Eric y Sreekar por tomarse el tiempo para profundizar en esto y explicarlo para todos! –

+1

Consulte también la publicación del blog [Delegados abiertos vs. Delegados cerrados] (http://blog.slaks.net/2011/06/open-delegates-vs-closed-delegates.html) que fue escrita por el autor de la pregunta (S. Laks) en un momento posterior. –

2

EDIT 2 No creo que esta respuesta ya, pero me dejó aquí para que el hilo podría tener todavía sentido y para que la gente ver por qué no es correcto. Vea mi otra respuesta para una opinión diferente sobre el asunto.

original

porque requeriría implícitamente boxeo el parámetro de tipo de valor receptor (porque el campo _target en el tipo System.Delegate que sostiene el receptor del parámetro es de tipo System.Object), lo que podría conducir a algún extraño comportamiento de alias si no lo esperabas.

EDITAR

hay algo más en juego aquí. Me encontré con este programa de ejemplo:

class Program 
{ 
    public static int Combine(int a, int b) 
    { 
     return a + b; 
    } 

    static void Main(string[] args) 
    { 
     var combineMethod = typeof(Program).GetMethod("Combine"); 
     var add4 = Delegate.CreateDelegate(
           typeof(Converter<int, int>), 
           4, 
           combineMethod) as Converter<int, int>; 

     for (int i = 0; i < 10; i++) 
     { 
      Console.WriteLine(add4(i)); 
     } 
     Console.ReadLine(); 
    } 
} 

y tengo una ArgumentException: "Error de unirse a secuencias diana método." en la llamada a CreateDelegate. No estoy seguro de por qué, y dado que el método relevante es un método internalcall, Reflector no es de mucha ayuda. El documentation for CreateDelegate tampoco fue de mucha ayuda. Estoy seguro de que tiene algo que ver con el boxeo del receptor, tal vez alguien con conocimiento de la fuente del Rotor podría ayudar a explicar por qué?

+0

¿Qué tipo de comportamiento de alias extraño? –

+0

¿Qué comportamiento de alias podría llevar a que no esperaras? Como el método de extensión ya toma el tipo de valor como un parámetro regular, no habría ninguna razón para suponer que no haría una copia. – SLaks

+0

Si llama al delegado dos veces, estaría en la misma copia encuadrada de la estructura. No estoy seguro de si eso califica como inesperado. –

Cuestiones relacionadas