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

运行时确定类型时如何注册并调用对应泛型Action/Command处理器

实现步骤与完整代码

1. 消除Handler实现的代码冗余

首先给泛型Handler添加抽象基类,通过显式接口实现非泛型的HandleAsync方法,内部自动完成类型校验和转调,所有具体Handler继承该基类后,无需重复编写非泛型接口的实现逻辑:

public abstract class ActionHandlerBase<T> : IActionHandler<T> where T : IAction, new()
{
    // 显式实现非泛型接口,对外不可见,无需子类重写
    async Task IActionHandler.HandleAsync(IAction action)
    {
        if (action is not T typedAction)
        {
            throw new ArgumentException($"参数类型不匹配,期望{typeof(T).Name},实际传入{action.GetType().Name}");
        }
        await HandleAsync(typedAction);
    }

    // 子类仅需实现该泛型处理方法
    public abstract Task HandleAsync(T action);
}

改造后的SetColorActionHandler可以简化为:

public class SetColorActionHandler : ActionHandlerBase<SetColorAction>
{
    public override async Task HandleAsync(SetColorAction action)
    {
        await ComponentManager.SetComponentColor(action);
    }
}

2. 实现ActionHandlerContainer分发容器

不要尝试在构造函数中注入IEnumerable<IActionHandler<T>>——T是开放泛型,DI容器无法直接解析所有闭合泛型的实现集合。正确做法是注入IServiceProvider,在构造阶段提取所有已注册的Handler构建映射字典,同时原生支持单Action多Handler、装饰器场景:

public class ActionHandlerContainer : IActionHandler
{
    // 存储类型对应的Handler列表,兼容多Handler场景
    private readonly Dictionary<Type, List<IActionHandler>> _handlers = new();

    public ActionHandlerContainer(IServiceProvider serviceProvider)
    {
        // 取出所有注册为IActionHandler的实例(所有泛型Handler都继承该接口,可被直接解析)
        var allHandlers = serviceProvider.GetServices<IActionHandler>()
            .Where(h => h.GetType().GetInterfaces()
                .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IActionHandler<>)));

        foreach (var handler in allHandlers)
        {
            // 解析当前Handler对应的Action类型
            var matchedActionType = handler.GetType()
                .GetInterfaces()
                .First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IActionHandler<>))
                .GetGenericArguments()[0];

            if (!_handlers.ContainsKey(matchedActionType))
            {
                _handlers[matchedActionType] = new List<IActionHandler>();
            }
            _handlers[matchedActionType].Add(handler);
        }
    }

    public async Task HandleAsync(IAction action)
    {
        var actionType = action.GetType();
        if (!_handlers.TryGetValue(actionType, out var matchedHandlers) || matchedHandlers.Count == 0)
        {
            throw new InvalidOperationException($"未找到类型{actionType.Name}对应的Action处理器");
        }

        // 按DI注册顺序依次执行所有匹配的Handler
        foreach (var handler in matchedHandlers)
        {
            await handler.HandleAsync(action);
        }
    }
}

之前动态获取泛型Handler的代码无法工作,是因为C#泛型类型参数必须在编译期确定,运行时生成泛型类型需要调用Type.MakeGenericType方法。但构造阶段预先生成映射字典的方案性能远高于每次请求动态反射解析,更适合MQTT消息处理这类高吞吐场景。

3. 待确认问题解答

  • 同Action多Handler支持:上述实现已经原生支持,同一个Action类型注册多个Handler时,会按照DI注册顺序依次执行。如果需要短路逻辑,可给IActionHandler增加返回值标识是否终止后续执行即可。
  • 装饰器模式支持:完全支持,注册时按依赖顺序注册即可。以日志装饰器为例:
public class ConsoleDebugLoggingSetColorActionHandler : ActionHandlerBase<SetColorAction>
{
    private readonly IActionHandler<SetColorAction> _innerHandler;

    public ConsoleDebugLoggingSetColorActionHandler(IActionHandler<SetColorAction> innerHandler)
    {
        _innerHandler = innerHandler;
    }

    public override async Task HandleAsync(SetColorAction action)
    {
        Console.WriteLine($"开始处理SetColorAction,目标颜色:{action.Color}");
        await _innerHandler.HandleAsync(action);
        Console.WriteLine("SetColorAction处理完成");
    }
}

服务注册代码参考:

// 先注册内部真实业务Handler
builder.Services.AddTransient<SetColorActionHandler>();
// 再注册装饰器,服务类型统一为IActionHandler<对应Action类型>
builder.Services.AddTransient<IActionHandler<SetColorAction>>(sp => 
    new ConsoleDebugLoggingSetColorActionHandler(sp.GetRequiredService<SetColorActionHandler>())
);
// 注册分发容器,服务类型写IActionHandler,避免和具体泛型Handler冲突
builder.Services.AddSingleton<IActionHandler, ActionHandlerContainer>();

4. MQTT场景适配说明

原有MQTT消息接收逻辑无需大幅调整,直接注入IActionHandler(即分发容器实例)即可,容器会自动匹配对应类型的Handler(包括装饰后的Handler链)执行:

public class MqttClientWrapper {
    private readonly IActionHandler _actionHandler;
    private readonly IActionFactory _actionFactory;

    // 构造函数注入依赖
    public MqttClientWrapper(IActionHandler actionHandler, IActionFactory actionFactory)
    {
        _actionHandler = actionHandler;
        _actionFactory = actionFactory;
    }

    private async Task ClientOnApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs arg) {
        Console.WriteLine("The client received an application message.");
        arg.DumpToConsole();
        
        var actionPayload = ActionPayload.FromPayload(arg.ApplicationMessage.Payload);
        var actionType = ActionMap.ActionIdentifierToActionType[actionPayload.ActionIdentifier];
        var action = _actionFactory.CreateAction(actionPayload.ActionData, actionType);
        
        // 直接调用即可,无需手动查找Handler
        await _actionHandler.HandleAsync(action);
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 11:03:28