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

构建方法装饰的表达式树?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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 09:06:17