构建方法装饰的表达式树?CommandHandler类实现技术问询
给CommandHandler的Handler方法构建表达式树装饰器
嘿,你已经把CommandHandler的核心映射逻辑跑通了——用反射把命令名和对应的Handler方法绑定成Func<Command, Task<object>>,现在要给这些方法加装饰器,用表达式树来实现对吧?这路子很对,比硬写装饰逻辑灵活太多了,我来给你捋捋具体怎么搞。
1. 先对齐现有基础逻辑
假设你的现有代码大概是这样的(如果有细节偏差你可以自行调整):
public class CommandHandler { private readonly Dictionary<string, Func<Command, Task<object>>> _commandMap = new(); public CommandHandler() { // 反射捞取所有符合命名规则的Handler方法 var handlerMethods = GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) .Where(m => m.Name.EndsWith("Handler") && m.ReturnType == typeof(Task<object>) && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == typeof(Command)); foreach (var method in handlerMethods) { var commandName = method.Name.Substring(0, method.Name.Length - "Handler".Length); // 原来直接创建委托的逻辑,现在要换成带装饰的表达式树版本 var func = (Func<Command, Task<object>>)Delegate.CreateDelegate(typeof(Func<Command, Task<object>>), this, method); _commandMap.Add(commandName, func); } } public Task<object> ExecuteCommand(Command command) { if (_commandMap.TryGetValue(command.Name, out var handler)) { return handler(command); } throw new InvalidOperationException($"No handler found for command: {command.Name}"); } // 示例Handler方法 public Task<object> TestHandler(Command command) { // 这里是你的业务逻辑 return Task.FromResult<object>("Test command executed successfully"); } } public class Command { public string Name { get; set; } // 其他命令相关属性 }
2. 表达式树装饰的核心思路
我们的目标是:给每个Handler方法生成一个包裹了装饰逻辑的新委托,替换原来直接创建的委托。常见的装饰场景比如日志、异常捕获、性能监控,这里我就以「执行前后日志+异常捕获」为例来演示,你可以按需替换成自己的装饰逻辑。
核心步骤就是:
- 拿到Handler方法的
MethodInfo - 构建表达式树的参数(也就是
Command类型的输入参数) - 生成调用原Handler方法的表达式节点
- 把装饰逻辑(日志、异常处理)用表达式树包裹在原方法调用的外层
- 把整个表达式树编译成
Func<Command, Task<object>>委托
3. 具体实现代码
3.1 封装装饰器表达式构建逻辑
先写一个通用方法,专门用来生成带装饰的表达式树,这样后续要改装饰逻辑只需要动这一块:
private Func<Command, Task<object>> CreateDecoratedHandler(MethodInfo handlerMethod) { // 1. 定义表达式的输入参数:Command command var commandParam = Expression.Parameter(typeof(Command), "command"); // 2. 生成调用原Handler方法的表达式:this.TestHandler(command) var instanceExpr = Expression.Constant(this); var callHandlerExpr = Expression.Call(instanceExpr, handlerMethod, commandParam); // 3. 给原方法调用加上装饰逻辑 var decoratedExpr = BuildDecoratorLogic(callHandlerExpr, commandParam); // 4. 把表达式编译成委托 return Expression.Lambda<Func<Command, Task<object>>>(decoratedExpr, commandParam).Compile(); } private Expression BuildDecoratorLogic(Expression originalCall, ParameterExpression commandParam) { // 执行前的日志逻辑 var logBeforeExpr = Expression.Call( typeof(Console), "WriteLine", Type.EmptyTypes, Expression.Add( Expression.Constant($"[Start] Executing command: "), Expression.Property(commandParam, nameof(Command.Name)) ) ); // 执行成功后的日志逻辑 var logAfterExpr = Expression.Call( typeof(Console), "WriteLine", Type.EmptyTypes, Expression.Add( Expression.Constant($"[End] Command executed: "), Expression.Property(commandParam, nameof(Command.Name)) ) ); // 异常捕获逻辑:捕获异常、打日志、重新抛出 var exceptionParam = Expression.Parameter(typeof(Exception), "ex"); var catchBlock = Expression.Catch( exceptionParam, Expression.Block( Expression.Call( typeof(Console), "WriteLine", Type.EmptyTypes, Expression.Add( Expression.Add( Expression.Constant($"[Error] Command failed: "), Expression.Property(commandParam, nameof(Command.Name)) ), Expression.Add( Expression.Constant(", Message: "), Expression.Property(exceptionParam, nameof(Exception.Message)) ) ) ), Expression.Rethrow(typeof(object)) ) ); // 把所有逻辑组合起来:先打开始日志,然后try-catch包裹原方法调用+结束日志 return Expression.Block( logBeforeExpr, Expression.TryCatch( Expression.Block(originalCall, logAfterExpr), catchBlock ) ); }
3.2 修改构造函数的映射逻辑
把原来直接创建委托的代码,替换成调用上面的CreateDecoratedHandler方法就可以了:
public CommandHandler() { var handlerMethods = GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) .Where(m => m.Name.EndsWith("Handler") && m.ReturnType == typeof(Task<object>) && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == typeof(Command)); foreach (var method in handlerMethods) { var commandName = method.Name.Substring(0, method.Name.Length - "Handler".Length); // 替换成带装饰的委托 var decoratedHandler = CreateDecoratedHandler(method); _commandMap.Add(commandName, decoratedHandler); } }
4. 一些扩展小贴士
- 灵活组合装饰器:如果需要多种装饰逻辑(比如先日志、再性能监控、再异常处理),可以把
BuildDecoratorLogic改成可扩展的,比如传入多个装饰器表达式,用组合模式把它们串起来。 - 异步方法的细节:因为我们的Handler是异步返回
Task<object>,表达式树编译出来的委托会自动兼容异步逻辑。如果你的装饰逻辑需要等待任务完成后执行(比如日志要在任务结束后打),可以用Expression.Await包裹原调用表达式,不过要注意表达式树的上下文要支持异步(我们这里返回的是Func<Command, Task<object>>,所以没问题)。 - 性能不用担心:表达式树是在构造函数中一次性编译完成的,运行时的性能和直接调用委托几乎没有差别,完全不会有反射的性能损耗。
内容的提问来源于stack exchange,提问作者dnf
相关产品推荐
相关产品推荐

