2011-04-14 7 views
6

Deseo crear un controlador que se pueda usar para manejar cualquier evento o delegado. En concreto, quiero ser capaz de escribir código, como a continuación:Crear un controlador catch-all para todos los eventos y delegados en C#

class Invoker 
{ 
    public object Invoke(object[] arg) 
    { 
     // generic handling code 
    } 
} 

static void Main() 
{ 
    var p = new Person(); 
    p.AddHandler("Event1", new Invoker().Invoke); 
} 

AddHandler es un método de extensión para object que reciben un nombre de evento y un delegado del tipo Func<object[], object>. Debería poder hacer cualquier magia para vincular el evento (por ejemplo, Event1 en este caso) al delegado proporcionado para que el delegado se invoque siempre que se active el evento.

La firma de Event1 no debería importar porque AddHandler debería funcionar con todo tipo de eventos (y delegados).

Sospecho que esto podría implicar algo de generación CIL para crear un delegado dinámico que coincida con el tipo del evento especificado (por ejemplo, Event1) y reenviar la llamada al delegado especificado (por ejemplo, new Invoker().Invoke). Pude construir un delegado dinámico, sin embargo, solo pude reenviar a métodos estáticos, no a métodos de instancia porque no pude encontrar una forma de insertar la instancia enlazada del método a ser invocado en la pila CLR (es decir, el Invoker instancia en el ejemplo). Consulte el código proporcionado a continuación para ver este problema con claridad (consulte la línea marcada con PROBLEMA).

Si alguien puede señalar una forma de mejorar el código de generación dinámica para capturar objetos encuadernados o mejor aún, sugerir una solución más simple que no necesita CIL, entonces es muy apreciada.

public static void AddHandler(this object target, string fieldName, 
    Func<object[], object> func) 
{ 
    var eventInfo = target.GetType().GetEvent(fieldName); 
    if (eventInfo != null) 
    { 
     Type delegateType = eventInfo.EventHandlerType; 
     var dynamicHandler = BuildDynamicHandler(target.GetType(), delegateType, func); 
     eventInfo.GetAddMethod().Invoke(target, new Object[] { dynamicHandler }); 
    } 
} 

public static Delegate BuildDynamicHandler(this Type delegateOwnerType, Type delegateType, 
    Func<object[], object> func) 
{ 
    MethodInfo invokeMethod = delegateType.GetMethod("Invoke"); 
    Type returnType = invokeMethod.ReturnType; 
    bool hasReturnType = returnType != Constants.VoidType; 
    var paramTypes = invokeMethod.GetParameters().Select(p => p.ParameterType).ToArray(); 
    var dynamicMethod = new DynamicMethod("add_handler", 
              hasReturnType ? returnType : null, paramTypes, delegateOwnerType); 

    var il = new EmitHelper(dynamicMethod.GetILGenerator()); 
    if (paramTypes.Length == 0) 
    { 
     il.ldnull.end(); 
    } 
    else 
    { 
     il.DeclareLocal(typeof(object[])); 
     il.ldc_i4(paramTypes.Length); 
     il.newarr(typeof(object)); 
     il.stloc_0.end(); 
     for (int i = 0; i < paramTypes.Length; i++) 
     { 
      il.ldloc_0 
       .ldc_i4(i) 
       .ldarg(i) 
       .boxIfValueType(paramTypes[i]) 
       .stelem_ref.end(); 
     } 
     il.ldloc_0.end(); 
    } 

    /////// ****************** ISSUE: work for static method only 
    il.call(func.Method); 
    if (hasReturnType) 
    { 
     il.unbox_any(returnType).ret(); 
    } 
    else 
    { 
     il.pop.ret(); 
    } 
    return dynamicMethod.CreateDelegate(delegateType); 
} 
+0

¿Podría proporcionar un poco de contexto acerca de por qué quiere hacer esto? – SimonC

+0

@SimonC: Estoy construyendo un lenguaje interpretado que podría interoperar con .NET. Deseo conectar funciones escritas en ese idioma a eventos .NET, p. clrObject.someEvent + = func() {...}; donde el lado derecho es una función pura de mi lenguaje. Una forma de hacerlo es haciendo que el intérprete cree un controlador genérico y lo agregue a clrObject.someEvent para que luego, cuando se active el evento, se invoque el controlador genérico (que a su vez usaría el intérprete para ejecutar la función). No estoy seguro de si hay una solución alternativa por ahora. –

Respuesta

4

Aquí es una implementación usando árboles de expresión:

public static Delegate BuildDynamicHandle(Type delegateType, Func<object[], object> func) 
    { 
     var invokeMethod = delegateType.GetMethod("Invoke"); 
     var parms = invokeMethod.GetParameters().Select(parm => Expression.Parameter(parm.ParameterType, parm.Name)).ToArray(); 
     var instance = func.Target == null ? null : Expression.Constant(func.Target); 
     var converted = parms.Select(parm => Expression.Convert(parm, typeof(object))); 
     var call = Expression.Call(instance, func.Method, Expression.NewArrayInit(typeof(object), converted)); 
     var body = 
      invokeMethod.ReturnType == typeof(void) ? (Expression)call : Expression.Convert(call, invokeMethod.ReturnType); 
     var expr = Expression.Lambda(delegateType, body, parms); 
     return expr.Compile(); 
    } 
+0

Excelente, ¡gracias! Esto es mucho más elegante que el enfoque CIL que uso. Creo que necesito deshacerme de mi hábito de usar inmediatamente CIL emitir para cualquier tarea genérica de código de tiempo de ejecución. ¿Puede recomendar artículos, libros y/o herramientas para ayudar con la programación del árbol de expresiones? –

+2

@Buu: lo aprendí simplemente jugando con la API. Es bastante sencillo, y siempre puedes inspeccionar los árboles de expresión generados por el compilador de C# si te preguntas cómo expresar algo. – kvb

+0

gracias, es bueno saberlo. –

2

¿Ha visto usar árboles de expresiones (http://msdn.microsoft.com/en-us/library/bb397951.aspx)? Hacen que sea mucho más fácil generar IL.

+0

Conozco el árbol exp. Sin embargo, creo que no importa qué mecanismo de código-gen seleccionado, el problema sigue siendo el mismo. –

+0

La respuesta de kvb demostró que tienes razón, debería haber mirado el árbol de expresiones antes de considerar emitir reflejo. El enfoque del árbol de expresiones es mucho más directo y probablemente no me hubiera encontrado con el problema que tenía en primer lugar si lo utilizo. +1. –

0

me funcionó una solución. Publiqué sobre esto con el código completo here, en caso de que alguien interesado en el enfoque de generación de CIL pura (que es no tan elegante como el enfoque de kvb).

Cuestiones relacionadas