如何用表达式树订阅带ref/out参数的动态事件并设置参数值
解决带ref/out参数的事件订阅与值回传问题
没问题,完全可以用表达式树实现这个需求!你原来的代码只处理了参数的“传入”,没考虑ref/out参数的“回传”——只要在表达式树里加上把Action修改后的数组值赋值回原参数的逻辑就行,我给你调整一下代码:
核心思路
- 把所有事件参数(包括ref/out)转成
object[]传给你的Action<object[]>; - 执行完你的业务逻辑后,从数组里取出被修改的ref/out参数值,转换回原类型并赋值给事件的原始参数;
- 因为数组是引用类型,你在
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
相关产品推荐
相关产品推荐

