c# - usar - Los métodos de extensión definidos en los tipos de valor no pueden utilizarse para crear delegados. ¿Por qué no?
tipos de delegates (2)
Se pueden asignar métodos de extensión a los delegados que coinciden con su uso en un objeto, como esto:
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 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
Error CS1113: los métodos de extensión ''FunnyExtension.Double (int)'' definidos en el tipo de valor ''int'' no se pueden usar para crear delegados.
¿Por qué no pueden?
En respuesta a mi otra respuesta, Eric Smith nota correctamente:
"... porque requeriría boxear implícitamente el parámetro de tipo de 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. Pruébalo para el tamaño:
Los métodos ordinarios de "instancia" en las estructuras toman, a nivel de CIL, un "puntero gestionado" (tipo &
) como parámetro de receptor. Esto es necesario para que los métodos de instancia en estructuras puedan asignarse a los campos de la estructura. Ver la Partición II, Sección 13.3 .
De manera similar, los métodos de instancia en clases toman una "referencia de objeto" (tipo O
) como un parámetro del receptor (la diferencia es que esto es un puntero al montón administrado, y debe ser rastreado para GC).
Ya que tanto CIL &
s como O
s pueden ser (y son) implementados por punteros, todo es perfecto 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ática que toma un int
como primer argumento requiere un argumento CIL de tipo int32
(vea la Partición III, sección 1.1.1). Aquí es donde las cosas se salen de los rieles. No veo ninguna razón por la que la implementación de los delegados no pudiera darse cuenta de que esto estaba sucediendo (por ejemplo, al inspeccionar los metadatos asociados con la información capturada de MethodInfo) y emitir un thunk que deshacería el _target
y Pase eso como el primer argumento, pero esto no es necesario para los delegados a los métodos de instancia clásicos en estructuras, ya que esperan un puntero de todos modos y no aparece (a juzgar por el ejemplo en mi respuesta incorrecta anterior) para ser implementado. Obviamente, el tipo de valor específico en cuestión controlaría la naturaleza exacta del procesador requerido.
A menos que me esté perdiendo un obstáculo más fundamental para la implementación (podría imaginar que plantearía problemas para el verificador, por ejemplo), parece un caso razonable para extender el tiempo de ejecución para respaldar este caso, pero todos los signos son apuntando a que esto es una limitación del tiempo de ejecución y no del compilador de C # per se.
EDIT 2 No creo más en esta respuesta, pero la dejé aquí para que el hilo aún tenga sentido y para que la gente vea por qué no está bien. Vea mi otra respuesta para una opinión diferente sobre el asunto.
Original
Porque requeriría encuadrar implícitamente el parámetro receptor del tipo de valor (porque el campo _getget en el tipo System.Delegate que contiene el parámetro del receptor es de tipo System.Object), lo que podría llevar a algún comportamiento extraño de aliasing si no estuviera esperando eso.
EDITAR
Hay algo más aquí. Ejecuté este programa de muestra:
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 obtuve una ArgumentException: "Error al enlazar con el método de destino". en la llamada a CreateDelegate. No estoy seguro de por qué, y debido a que el método relevante es un método de llamada internalcall
, Reflector no es de mucha ayuda. La documentación para 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 de Rotor podría ayudar a explicar por qué.