为C#中的所有事件和委托创建一个catch-all处理程序

时间:2011-04-14 04:59:18

标签: c# .net events delegates cil

我想创建一个可用于处理任何事件或委托的处理程序。具体来说,我希望能够编写如下代码:

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

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

AddHandlerobject的扩展方法,它接收事件名称和类型Func<object[], object>的委托。它应该能够做任何魔术来将事件(例如,在这种情况下为Event1)绑定到提供的委托,以便在触发事件时调用委托。

Event1的签名无关紧要,因为AddHandler应该适用于所有类型的事件(和代理人)。

我怀疑这可能涉及一些CIL生成来构建匹配指定事件类型的动态委托(例如Event1)并将调用转发给指定的委托(例如new Invoker().Invoke)。我能够构建这样一个动态委托,但是它只能转发到静态方法,而不是实例方法,因为我找不到将待调用方法的绑定实例推送到CLR堆栈的方法(即示例中的Invoker实例)。请参阅下面提供的代码以清楚地查看此问题(请参阅标有ISSUE的行。)

如果有人能指出一种方法来改进动态生成代码以捕获绑定对象或更好,建议一个更简单的解决方案,不需要CIL,那么非常感谢。

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);
}

3 个答案:

答案 0 :(得分:5)

这是使用表达式树的实现:

    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();
    }

答案 1 :(得分:2)

您是否考虑过使用表达式树(http://msdn.microsoft.com/en-us/library/bb397951.aspx)?它们使生成IL更容易。

答案 2 :(得分:0)

我制定了一个解决方案。如果有人对纯CIL生成方法感兴趣(优于kvb的方法),我会用完整的代码here写博客。