在 C# 中为所有事件和委托创建一个包罗万象的处理程序

发布于 2024-11-01 08:53:14 字数 2728 浏览 4 评论 0原文

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

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。它应该能够执行任何魔法将事件(例如本例中的 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);
}

I want to create a handler that can be used to handle any event or delegate. Specifically, I want to be able to write code like below:

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

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

AddHandler is an extension method for object which receive an event name and a delegate of type Func<object[], object>. It should be able to do whatever magic to bind the event (e.g. Event1 in this case) to the provided delegate so that the delegate is invoked whenever the event is fired.

The signature of Event1 shouldn't matter because AddHandler should work with all types of events (and delegates).

I suspect this might involve some CIL generation to build a dynamic delegate matching the type of the specified event (e.g. Event1) and forwarding the call to the specified delegate (e.g. new Invoker().Invoke). I was able to build such a dynamic delegate, however it could only forward to static methods, not instance methods because I couldn't find a way to push the bound instance of the to-be-invoked method into the CLR stack (i.e. the Invoker instance in the example). See the code provided below to see this issue clearly (see the line marked with ISSUE).

If anyone could point out a way to improve the dynamic generation code to capture bound object or better yet, suggest a simpler solution which doesn't need CIL then it is much appreciated.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

静水深流 2024-11-08 08:53:14

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

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

Here's an implementation using expression trees:

    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();
    }
输什么也不输骨气 2024-11-08 08:53:14

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

Have you looked at using expression trees (http://msdn.microsoft.com/en-us/library/bb397951.aspx)? They make it much easier to generate IL.

游魂 2024-11-08 08:53:14

我想出了一个解决方案。我在博客上写了完整的代码这里,以防有人对纯 CIL 生成方法感兴趣(这不像像 kvb 的方法那么优雅)。

I worked out a solution. I blogged about it with the full code here, in case anyone interested in the pure CIL generation approach (which is not as elegant as kvb's approach).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文