2009-07-31 10 views
5

¿Se puede crear un delegado de un método de instancia sin especificar la instancia en el momento de la creación? En otras palabras, ¿puedes crear un delegado "estático" que tome como primer parámetro la instancia en la que se debe invocar el método?"Despliegue" de un método de instancia en .NET

Por ejemplo, ¿cómo puedo construir el siguiente delegado utilizando la reflexión?

Func<int, string> = i=>i.ToString(); 

Soy consciente del hecho de que puedo usar MethodInfo.Invoke, pero esto es más lento, y no comprueba para el tipo-corrección hasta que se llama.

Cuando se tiene la MethodInfo de un determinado método estático , es posible construir un delegado utilizando Delegate.CreateDelegate(delegateType, methodInfo), y todos los parámetros del método estático permanecer libre.

Como señaló Jon Skeet, simplemente puede aplicar lo mismo para hacer un delegado abierto de un método de instancia si el método no es virtual en un tipo de referencia. Decidir qué método utilizar en un método virtual es complicado, por lo que no es tan trivial, y los tipos de valor parecen no funcionar en absoluto.

Para los tipos de valor, CreateDelegate muestra un comportamiento muy extraño:

var func37 = (Func<CultureInfo,string>)(37.ToString); 
var toStringMethod = typeof(int).GetMethod("ToString", BindingFlags.Instance | BindingFlags.Public, null, new Type[] {typeof(CultureInfo) }, null); 
var func42 = (Func<CultureInfo,string>)Delegate.CreateDelegate(typeof(Func<CultureInfo,string>), 42, toStringMethod,true); 
Console.WriteLine(object.ReferenceEquals(func37.Method,func42.Method)); //true 
Console.WriteLine(func37.Target);//37 
Console.WriteLine(func42.Target);//42 
Console.WriteLine(func37(CultureInfo.InvariantCulture));//37 
Console.WriteLine(func42(CultureInfo.InvariantCulture));//-201040128... WTF? 

Calling CreateDelegate con null como el objeto de destino emite una excepción de unión si el método de instancia pertenecía a un tipo de valor (esto funciona para los tipos de referencia).

Algunos años de seguimiento después: La diana unido de forma incorrecta, que causó func42(CultureInfo.InvariantCulture); para volver "-201040128" en lugar de "42" en mi ejemplo era una corrupción de memoria que podría haber permitido la ejecución remota de código (cve-2010-1898); esto se solucionó en 2010 en la actualización de seguridad ms10-060. ¡Los marcos actuales imprimen correctamente 42! Eso no facilita la respuesta a esta pregunta, pero explica el comportamiento particularmente extraño en el ejemplo.

Respuesta

9

En realidad se ha elegido un ejemplo particularmente difícil, por dos razones:

  • ToString() es un método virtual heredado de object pero anulado en Int32.
  • int es un tipo de valor, y no son extrañas reglas con Delegate.CreateDelegate() cuando se trata de tipos de valores y métodos de instancia -, básicamente, el primer parámetro efectiva se convierte en ref int en lugar de int

Sin embargo, he aquí un ejemplo de String.ToUpper, que no tiene ninguno de esos problemas:

using System; 
using System.Reflection; 

class Test 
{ 
    static void Main() 
    { 
     MethodInfo method = typeof(string).GetMethod 
      ("ToUpper", BindingFlags.Instance | BindingFlags.Public, 
      null, new Type[]{}, null); 

     Func<string, string> func = (Func<string, string>) 
      Delegate.CreateDelegate(typeof(Func<string, string>), 
            null, 
            method); 

     string x = func("hello"); 

     Console.WriteLine(x); 
    } 
} 

Si eso es lo suficientemente bueno para ti, gran ... si realmente quiere int.ToString, voy a tener que esforzarse un poco más :)

He aquí un ejemplo de un tipo de valor, utilizando un nuevo tipo de delegado que toma su primer parámetro por referencia:

using System; 
using System.Reflection; 

public struct Foo 
{ 
    readonly string value; 

    public Foo(string value) 
    { 
     this.value = value; 
    } 

    public string DemoMethod() 
    { 
     return value; 
    } 
} 

class Test 
{ 
    delegate TResult RefFunc<TArg, TResult>(ref TArg arg); 

    static void Main() 
    { 
     MethodInfo method = typeof(Foo).GetMethod 
      ("DemoMethod", BindingFlags.Instance | BindingFlags.Public, 
      null, new Type[]{}, null); 
     RefFunc<Foo, string> func = (RefFunc<Foo, string>) 
      Delegate.CreateDelegate(typeof(RefFunc<Foo, string>), 
            null, 
            method); 

     Foo y = new Foo("hello"); 
     string x = func(ref y); 

     Console.WriteLine(x); 
    } 
} 
+1

Este es uno de esos casos en los que queda claro que C# todavía tiene espacio para crecer como lenguaje funcional. Tratar las funciones como ciudadanos de primera clase todavía no es tan fluida como nos gustaría. ¿Hay alguna forma de explotar las características dinámicas en C# 4 para facilitar este tipo de cosas? – LBushkin

+1

@LBushkin: No lo creo. De hecho, la tipificación dinámica y las lambdas no van muy bien juntas para empezar: el compilador debe saber a qué tipo convertir la expresión lambda en el momento de la compilación. –

+0

I * had * estado probando int.ToString, pero para el uso real supongo que podría prescindir de los métodos virtuales, aunque no sin estructuras. De todos modos, gracias por el aviso, pasé por alto la complejidad de los métodos virtuales, y el mensaje de error no es exactamente informativo ... –

3

No estoy seguro, pero puede ser Open delegates puede ayudarlo.

Upd: Siga este link, si el primero no funciona.

+0

Ese enlace lleva a una página 404 - tal vez haya cometido un error? –

+0

Link funciona bien para mí. –

+1

Extraño, al hacer clic en el enlace me lleva a una página 404, al actualizar la página aparece el mismo 404 - Pero al presionar Intro en la barra de la url (luego, quitando el referer) aparece la página. Algunos errores del navegador/servidor, al parecer (solo FF3.5.1 - Chrome funciona bien). De todos modos, encontré la página ahora ;-) –

0

La forma de Goog puede estar utilizando el tipo "dinámico" en .NET 4.0. Sin embargo, el Delegado necesita la instancia (para métodos no estáticos). Los problemas son más complejos a continuación lokks en un primer momento debido a polymorfism etc ...

2

Usted podría utilizar Lambdas para conseguir un envoltorio estática "algo" compilado para su método de instancia.

El ejemplo siguiente no es sorprendentemente rápido, sin embargo, debe ser significativamente más rápido que cualquier invocación dinámica simple.

La salida

100000 iterations took 4 ms 
1000000 iterations took 18 ms 
10000000 iterations took 184 ms 

El código

class Program 
{ 

    public sealed class Test 
    { 
     public String Data { get; set; } 
     public override string ToString() 
     { 
     return Data; 
     } 
    } 

    static void Main(string[] args) 
    { 
     TestRun(100000); 
     TestRun(1000000); 
     TestRun(10000000); 
    } 

    private static void TestRun(int iterations) 
    { 
     var toString = typeof(Test).GetMethod("ToString", 
              BindingFlags.Instance 
              | BindingFlags.Public, 
              null, 
              Type.EmptyTypes, 
              null); 
     var call = GetCall<Test, String>(toString); 
     var tests 
     = (from i in Enumerable.Range(1, iterations) 
      select new Test { Data = "..." + i }).ToList(); 

     var sw = Stopwatch.StartNew(); 
     tests.ForEach(i => call(i)); 
     sw.Stop(); 
     Console.WriteLine("{0} iterations took {1} ms", iterations, sw.ElapsedMilliseconds); 
    } 

    private static Func<T, M> GetCall<T, M>(MethodInfo methodInfo) 
    { 
     var input = Expression.Parameter(typeof(T), "input"); 
     MethodCallExpression member = Expression.Call(input, methodInfo); 
     var lambda = Expression.Lambda<Func<T, M>>(member, input); 

     return lambda.Compile(); 
    } 
} 
+0

¡Esa es una buena idea, gracias! –

Cuestiones relacionadas