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

如何用表达式树订阅带ref/out参数的动态事件并设置参数值

解决带ref/out参数的事件订阅与值回传问题

没问题,完全可以用表达式树实现这个需求!你原来的代码只处理了参数的“传入”,没考虑ref/out参数的“回传”——只要在表达式树里加上把Action修改后的数组值赋值回原参数的逻辑就行,我给你调整一下代码:

核心思路

  1. 把所有事件参数(包括ref/out)转成object[]传给你的Action<object[]>;
  2. 执行完你的业务逻辑后,从数组里取出被修改的ref/out参数值,转换回原类型并赋值给事件的原始参数;
  3. 因为数组是引用类型,你在Action里修改数组元素的操作会直接反映到表达式树的后续逻辑中。

修改后的表达式树创建方法

public static Delegate CreateProxyWithDynamicParameters(this EventInfo eventInfo, Action<object[]> action)
{
    var eventHandlerType = eventInfo.EventHandlerType;
    var invokeMethod = eventHandlerType.GetMethod("Invoke");
    var parameters = invokeMethod.GetParameters().ToArray();
    var parameterExpressions = parameters.Select(p => Expression.Parameter(p.ParameterType, p.Name)).ToArray();

    // 1. 将所有参数(含ref/out)转换为object数组
    var convertedParams = parameters.Select((param, idx) =>
    {
        Expression paramExpr = parameterExpressions[idx];
        // 对于ref/out参数,直接转换其引用的值为object
        if (param.ParameterType.IsByRef)
        {
            paramExpr = Expression.Convert(paramExpr, typeof(object));
        }
        else
        {
            paramExpr = Expression.Convert(paramExpr, typeof(object));
        }
        return paramExpr;
    }).ToArray();

    var arrayExpr = Expression.NewArrayInit(typeof(object), convertedParams);

    // 2. 调用你的业务Action
    var actionInvokeExpr = Expression.Call(Expression.Constant(action), nameof(Action<object[]>.Invoke), null, arrayExpr);

    // 3. 把数组中被修改的ref/out参数值回写到原始参数
    var assignExpressions = new List<Expression>();
    foreach (var (param, paramExpr, idx) in parameters.Zip(parameterExpressions, Enumerable.Range(0, parameters.Length)))
    {
        if (param.IsOut || param.ParameterType.IsByRef)
        {
            // 取出数组中对应位置的元素
            var arrayElement = Expression.ArrayAccess(arrayExpr, Expression.Constant(idx));
            // 转换为参数的实际类型(去掉ref的包装)
            var targetType = param.ParameterType.GetElementType();
            var convertedValue = Expression.Convert(arrayElement, targetType);
            // 赋值回原ref/out参数
            assignExpressions.Add(Expression.Assign(paramExpr, convertedValue));
        }
    }

    // 组合所有步骤为一个代码块
    var body = Expression.Block(
        actionInvokeExpr,
        Expression.Block(assignExpressions)
    );

    var lambda = Expression.Lambda(eventHandlerType, body, parameterExpressions);
    return lambda.Compile();
}

如何使用(以你的Inventor示例为例)

在你的EventAction里,直接修改数组对应位置的元素即可设置out参数的值:

protected static void AddEvent<TMessage>(EventInfo eventInfo) where TMessage : EventArgs {
    void EventAction(object[] e) {
        // 处理其他参数逻辑...
        // 第4个参数是out HandlingCodeEnum,对应数组索引3
        e[3] = HandlingCodeEnum.kEventHandled;
    }
    var @delegate = eventInfo.CreateProxyWithDynamicParameters(EventAction);
    DelegateDictionary[typeof(TMessage)] = @delegate;
    eventInfo.AddEventHandler(x.Target, @delegate);
}

关键细节说明

  • ref/out参数的识别:通过ParameterInfo.IsOut和ParameterType.IsByRef判断哪些参数需要回写;
  • 类型转换:ref/out参数的类型是T&,需要用GetElementType()拿到实际的T类型再转换;
  • 数组引用特性:因为object[]是引用类型,你在Action里修改数组元素的操作会直接被表达式树后续的回写逻辑读取到。

这样调整后,不管是普通参数还是带ref/out的参数,都能完美支持——既能把参数传给你的业务逻辑,也能把你设置的值回传给事件触发方。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 18:27:51