You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让事件处理程序委托知晓调用它的事件名称

问题:在通用事件处理程序中获取触发事件的名称

我的代码提炼出了核心问题(来自一个更大的单元测试项目):

// 为myInstance中的所有事件定义所需的委托
EventHandler oea = (o, ea) => GenericEventHandler(o, ea);
EventHandler<int> oi = (o, i) => GenericEventHandler(o, i);
// 更多委托定义

myInstance.GetType().GetEvents().Foreach(ei => {
  // 此处检查EventInfo的委托以确定使用哪个处理程序
  ....
  AssignEvent(myItem, ei, oea); // 或者
  AssignEvent(myItem, ei, oi);  // 或其他委托
  ....
});
...
private static void AssignEvent(object instance, EventInfo @event, Delegate handler)
{
    var iName = instance.GetType().Name;
    WriteTrace($"Assign {@event.EventHandlerType.Name} to {iName}.{@event.Name}");
    @event.AddEventHandler(instance, handler);
}
private static void GenericEventHandler(params object[] o)
{
    WriteTrace("$Event from {....} invoked");
}

我希望在运行时知晓调用该处理程序的事件名称。@event.Name就在那里,但如何让处理程序获取到它呢?

由于事件处理程序是通过这种方式绑定的,GenericEventHandler内部的StackTrace无法提供事件名称的线索。

我已经尝试过DynamicMethod、反射、表达式等多种方式,但都未能找到可行方案。

我希望避免使用Emit和IL技术。


解决方案:通过闭包+表达式树传递事件名称

核心思路是在绑定每个事件时,将event.Name通过闭包嵌入到事件处理程序中,同时用表达式树适配不同类型的事件委托,无需手动定义每种委托的重载。

步骤1:修改通用处理方法,增加事件名称参数

private static void GenericEventHandler(string eventName, params object[] args)
{
    var sender = args[0];
    WriteTrace($"Event from {eventName} invoked (Sender: {sender})");
}

步骤2:编写动态创建带事件名称的委托的辅助方法

private static Delegate CreateHandlerWithEventName(string eventName, Type handlerType)
{
    // 获取委托的Invoke方法
    var invokeMethod = handlerType.GetMethod("Invoke") 
        ?? throw new ArgumentException("Invalid delegate type");
    
    // 构建委托的参数表达式
    var paramExpressions = invokeMethod.GetParameters()
        .Select(p => Expression.Parameter(p.ParameterType))
        .ToArray();
    
    // 构建调用GenericEventHandler的表达式:传入事件名称+所有参数的object数组
    var eventNameExpr = Expression.Constant(eventName);
    var argsArrayExpr = Expression.NewArrayInit(
        typeof(object), 
        paramExpressions.Select(p => Expression.Convert(p, typeof(object)))
    );
    var callExpr = Expression.Call(
        typeof(YourClassName), // 替换成包含GenericEventHandler的类名
        nameof(GenericEventHandler), 
        null, 
        eventNameExpr, 
        argsArrayExpr
    );
    
    // 编译为目标类型的委托
    var lambda = Expression.Lambda(handlerType, callExpr, paramExpressions);
    return lambda.Compile();
}

步骤3:修改事件绑定逻辑

// 替换原来的委托定义和遍历逻辑
myInstance.GetType().GetEvents().ToList().ForEach(ei => {
    var handler = CreateHandlerWithEventName(ei.Name, ei.EventHandlerType);
    AssignEvent(myInstance, ei, handler);
});

原理说明

  • 每个事件绑定的委托都会通过闭包捕获当前事件的ei.Name,当事件触发时,该名称会被自动传入GenericEventHandler。
  • 表达式树自动适配不同的事件委托类型(如EventHandler、EventHandler<int>等),无需手动编写每种委托的匿名方法。
  • 完全避免了Emit/IL操作,仅使用表达式树实现动态委托创建。

内容的提问来源于stack exchange,提问作者ThinkAgain

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 13:24:50