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

C#:实现支持注册接受派生类型委托的Holder类的最优方案

类型安全的指令委托分发实现(观察者模式变种)

你遇到的问题本质是要实现类型安全的事件订阅与分发机制,属于观察者模式的一种进阶应用——核心目标是让订阅者只关心自己感兴趣的指令类型,同时避免在处理函数里做繁琐的类型强制转换。

原来的代码编译失败,是因为C#里委托的参数是逆变的:你不能把一个接受InstructionA(派生类)的委托赋值给接受Instruction(基类)的委托变量(这相当于要求基类实例能隐式转成派生类,显然不成立)。我们可以通过泛型来解决这个问题,让整个流程完全类型安全。

正确的实现方案

下面是完整的可编译实现,包含泛型委托、类型安全的注册方法,以及自动匹配指令类型的分发逻辑:

using System;
using System.Collections.Generic;

// 基类指令
abstract class Instruction { }
class InstructionA : Instruction { }
class InstructionB : Instruction { }

// 泛型委托:专门处理指定类型的指令
delegate void InstructionHandler<T>(T instruction) where T : Instruction;

class InstructionHolder {
    // 内部存储:键是指令类型,值是对应的泛型委托列表(用object存储,后续通过泛型转换)
    private readonly Dictionary<Type, List<object>> _handlers = new Dictionary<Type, List<object>>();

    // 泛型注册方法:让订阅者注册针对特定指令类型的处理函数
    public void RegisterHandler<T>(InstructionHandler<T> handler) where T : Instruction {
        var instructionType = typeof(T);
        if (!_handlers.TryGetValue(instructionType, out var handlerList)) {
            handlerList = new List<object>();
            _handlers[instructionType] = handlerList;
        }
        handlerList.Add(handler);
    }

    // 处理指令:自动匹配对应的处理函数并执行
    public void ProcessInstruction(Instruction instruction) {
        var instructionType = instruction.GetType();
        if (_handlers.TryGetValue(instructionType, out var handlerList)) {
            // 遍历并执行所有匹配的处理函数
            foreach (var handlerObj in handlerList) {
                if (handlerObj is InstructionHandler<T> handlerTyped) {
                    // 这里的类型转换是安全的,因为我们是按指令类型存储的
                    handlerTyped((T)instruction);
                }
            }
        }
    }
}

// 示例订阅者类
class SomeClass {
    public SomeClass(InstructionHolder holder) {
        // 注册针对InstructionA的处理函数,完全类型安全
        holder.RegisterHandler<InstructionA>(HandleInstructionA);
    }

    private void HandleInstructionA(InstructionA a) {
        Console.WriteLine($"处理InstructionA: {a.GetType().Name}");
    }
}

class OtherClass {
    public OtherClass(InstructionHolder holder) {
        holder.RegisterHandler<InstructionA>(HandleInstructionA);
        holder.RegisterHandler<InstructionB>(HandleInstructionB);
    }

    private void HandleInstructionA(InstructionA a) {
        Console.WriteLine($"OtherClass处理InstructionA: {a.GetType().Name}");
    }

    private void HandleInstructionB(InstructionB b) {
        Console.WriteLine($"OtherClass处理InstructionB: {b.GetType().Name}");
    }
}

// 测试代码
class Program {
    static void Main() {
        var holder = new InstructionHolder();
        var some = new SomeClass(holder);
        var other = new OtherClass(holder);

        holder.ProcessInstruction(new InstructionA());
        holder.ProcessInstruction(new InstructionB());
    }
}

关键设计点说明

  • 泛型委托InstructionHandler<T>:确保处理函数只能接受指定的Instruction派生类,从根源上避免类型转换的需求。
  • 泛型注册方法RegisterHandler<T>:订阅者明确指定要处理的指令类型,Holder内部按类型分组存储委托,保证类型匹配。
  • 安全的分发逻辑:在ProcessInstruction中,通过instruction.GetType()获取实际类型,找到对应的委托列表后,通过泛型转换执行处理函数——这里的类型转换是完全安全的,因为我们是按指令类型存储的对应委托。

额外优化建议

  • 如果需要支持基类指令的订阅(比如订阅所有Instruction类型的指令),可以在分发逻辑中遍历类型的继承链,找到所有匹配的委托列表(比如先找InstructionA,再找Instruction)。
  • 可以添加取消订阅的方法,通过存储委托的引用或者唯一标识来移除特定的处理函数。
  • 可以考虑用ConcurrentDictionary来保证多线程环境下的线程安全。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.01 03:27:37