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);
}
¿Podría proporcionar un poco de contexto acerca de por qué quiere hacer esto? – SimonC
@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. –