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

如何创建含泛型列表的字典?事件总线类型适配问题求助

事件总线泛型订阅类型转换问题解决方案

问题分析

你遇到的cannot convert from Subscription<T> to Subscription<IEventCallback>错误,核心原因是泛型默认不支持协变。即便T继承自IEventCallback,Subscription<T>与Subscription<IEventCallback>仍属于不同的封闭泛型类型,编译器不会自动允许转换。

你用List<Object>的方案虽能运行,但存在明显缺陷:类型不安全、依赖强制转换、无法在编译期排查错误,且原Unsubscribe方法存在逻辑错误(_subscribers[type].Remove(type)应为移除action而非type)。

优雅解决方案:利用泛型协变

通过定义协变泛型接口,可在保持类型安全的前提下解决转换问题。

步骤1:定义协变订阅接口

创建带out关键字的协变接口,标记泛型参数为协变类型,这样当T是IEventCallback的子类时,ISubscription<T>可隐式转换为ISubscription<IEventCallback>:

public interface IEventCallback { }

// 协变接口,out关键字允许泛型类型向上转换
public interface ISubscription<out T> where T : IEventCallback
{
    void Publish(T callback);
}

步骤2:实现Subscription类

让Subscription<T>实现上述协变接口:

public class Subscription<T> : ISubscription<T> where T : IEventCallback
{
    private readonly Action<T> _action;

    public Subscription(Action<T> action)
    {
        _action = action ?? throw new ArgumentNullException(nameof(action));
    }

    public void Publish(T callback)
    {
        _action.Invoke(callback);
    }
}

步骤3:重构EventBus类

将Dictionary类型改为Dictionary<Type, List<ISubscription<IEventCallback>>>,即可安全添加Subscription<T>实例:

public class EventBus
{
    private readonly Dictionary<Type, List<ISubscription<IEventCallback>>> _subscribers = new();

    // 保留原有单例实现
    public static EventBus Bus => INSTANCE.Value;
    private static readonly Lazy<EventBus> INSTANCE = new(() => new EventBus());

    public void Subscribe<T>(Action<T> action) where T : IEventCallback
    {
        ArgumentNullException.ThrowIfNull(action);
        var eventType = typeof(T);

        if (!_subscribers.ContainsKey(eventType))
        {
            _subscribers[eventType] = new List<ISubscription<IEventCallback>>();
        }

        // 协变支持隐式转换,无编译错误
        _subscribers[eventType].Add(new Subscription<T>(action));
    }

    public void Unsubscribe<T>(Action<T> action) where T : IEventCallback
    {
        ArgumentNullException.ThrowIfNull(action);
        var eventType = typeof(T);

        if (_subscribers.TryGetValue(eventType, out var subscriptions))
        {
            // 通过反射查找对应Action的Subscription实例并移除
            var toRemove = subscriptions.OfType<Subscription<T>>()
                .FirstOrDefault(s => s.GetType().GetField("_action", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(s) == action);
            
            if (toRemove != null)
            {
                subscriptions.Remove(toRemove);
            }
        }
    }

    public void Publish<T>(T eventData) where T : IEventCallback
    {
        ArgumentNullException.ThrowIfNull(eventData);
        var eventType = typeof(T);

        if (_subscribers.TryGetValue(eventType, out var subscriptions))
        {
            // 转换为具体泛型类型后触发事件
            foreach (var subscription in subscriptions.Cast<ISubscription<T>>())
            {
                subscription.Publish(eventData);
            }
        }
    }
}

关键说明

  1. 协变接口:out T使ISubscription<T>可向上转换为ISubscription<IEventCallback>,彻底解决类型转换错误。
  2. 类型安全:全程无需强制转换(Unsubscribe的反射仅为保持封装,若允许暴露_action可简化逻辑)。
  3. 修复Unsubscribe错误:原代码中移除type的逻辑错误已修正为查找对应Subscription实例。

简化方案:直接存储包装后的Action

若无需Subscription类封装,可直接将Action<T>包装为Action<IEventCallback>,Dictionary存储List<Action<IEventCallback>>:

public class EventBus
{
    private readonly Dictionary<Type, List<(Action<T> Original, Action<IEventCallback> Wrapped)>> _subscribers = new();

    public static EventBus Bus => INSTANCE.Value;
    private static readonly Lazy<EventBus> INSTANCE = new(() => new EventBus());

    public void Subscribe<T>(Action<T> action) where T : IEventCallback
    {
        ArgumentNullException.ThrowIfNull(action);
        var eventType = typeof(T);

        if (!_subscribers.ContainsKey(eventType))
        {
            _subscribers[eventType] = new List<(Action<T>, Action<IEventCallback>)>();
        }

        // 包装Action<T>为Action<IEventCallback>
        Action<IEventCallback> wrapped = callback => action((T)callback);
        _subscribers[eventType].Add((action, wrapped));
    }

    public void Publish<T>(T eventData) where T : IEventCallback
    {
        ArgumentNullException.ThrowIfNull(eventData);
        var eventType = typeof(T);

        if (_subscribers.TryGetValue(eventType, out var actions))
        {
            foreach (var (_, wrapped) in actions)
            {
                wrapped.Invoke(eventData);
            }
        }
    }

    public void Unsubscribe<T>(Action<T> action) where T : IEventCallback
    {
        ArgumentNullException.ThrowIfNull(action);
        var eventType = typeof(T);

        if (_subscribers.TryGetValue(eventType, out var actions))
        {
            var toRemove = actions.FirstOrDefault(pair => pair.Original == action);
            if (toRemove.Original != null)
            {
                actions.Remove(toRemove);
            }
        }
    }
}

此方案更简洁,通过存储原始Action与包装Action的元组,解决了Unsubscribe的查找问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 16:20:53