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

C#泛型委托类型转换错误:无法将Action<T>转为Action<BaseEvent>

泛型事件处理的类型转换错误及无强制转换实现方案

现有代码

CycleManager类

public class CycleManager
{
    private ConcurrentQueue<BaseCommand> m_Commands = new ConcurrentQueue<BaseCommand>();
    private EventContainer<BaseEvent> m_Container = new EventContainer<BaseEvent>();
    public void RunCommand(BaseCommand command)
    {
        m_Commands.Enqueue(command);
    }

    public void WaitForEvent<T>(Action<T> handler) where T : BaseEvent
    {
        m_Container.Add(handler);
    }
}

EventContainer类

public class EventContainer<TEvent> where TEvent : BaseEvent
{
    private Dictionary<Type, Action<TEvent>> m_eventHandlers = new Dictionary<Type, Action<TEvent>>();

    public void Add(Action<TEvent> handler)
    {
        m_eventHandlers.Add(typeof(TEvent), handler);
    }
}

错误信息

在执行m_Container.Add(handler);时出现编译错误:

Argument 1: cannot convert from 'System.Action' to 'System.Action<CycleManagerTesting.Models.BaseEvent>'

期望的调用方式

希望实现无需强制转换事件类型的调用,直接访问子类事件的属性:

var c = new CycleManager();
c.WaitForEvent<DoorOpenedEvent>(e =>
{
    // 直接操作e.DoorID,无需强制转换
    Console.WriteLine($"门ID:{e.DoorID}");
});

现有不满意的方案

若修改EventContainer的Add方法参数为Action<BaseEvent>,虽然能编译通过,但调用时必须强制转换事件类型,代码冗余:

var c = new CycleManager();
c.WaitForEvent<DoorOpenedEvent>(e =>
{
    var realEvent = (DoorOpenedEvent)e;
    // 操作realEvent.DoorID
});

相关基础类定义:

public class DoorOpenedEvent : BaseEvent
{
    public string DoorID { get; set; }
    public string Status { get; set; }
}

public class BaseEvent { }
public class BaseCommand { }

解决方案

调整EventContainer的实现,改用Dictionary<Type, Delegate>存储不同类型的事件处理器,同时提供泛型的Add方法,从根源避免类型转换问题:

修改后的EventContainer类

public class EventContainer
{
    private readonly Dictionary<Type, Delegate> _eventHandlers = new Dictionary<Type, Delegate>();

    public void Add<TEvent>(Action<TEvent> handler) where TEvent : BaseEvent
    {
        var eventType = typeof(TEvent);
        // 支持同一事件类型添加多个处理器
        if (_eventHandlers.ContainsKey(eventType))
        {
            _eventHandlers[eventType] = Delegate.Combine(_eventHandlers[eventType], handler);
        }
        else
        {
            _eventHandlers.Add(eventType, handler);
        }
    }

    // 补充事件触发方法,用于分发事件
    public void Trigger<TEvent>(TEvent eventInstance) where TEvent : BaseEvent
    {
        if (_eventHandlers.TryGetValue(typeof(TEvent), out var handler))
        {
            ((Action<TEvent>)handler)?.Invoke(eventInstance);
        }
    }
}

修改后的CycleManager类

public class CycleManager
{
    private ConcurrentQueue<BaseCommand> m_Commands = new ConcurrentQueue<BaseCommand>();
    private EventContainer m_Container = new EventContainer();

    public void RunCommand(BaseCommand command)
    {
        m_Commands.Enqueue(command);
    }

    public void WaitForEvent<T>(Action<T> handler) where T : BaseEvent
    {
        m_Container.Add(handler);
    }

    // 可选:对外提供触发事件的方法(根据业务场景调整)
    public void TriggerEvent<T>(T eventInstance) where T : BaseEvent
    {
        m_Container.Trigger(eventInstance);
    }
}

验证调用

现在可以直接按期望的方式调用,无需任何强制转换:

var c = new CycleManager();
c.WaitForEvent<DoorOpenedEvent>(e =>
{
    Console.WriteLine($"门{e.DoorID}已打开,状态:{e.Status}");
});

// 测试触发事件
c.TriggerEvent(new DoorOpenedEvent { DoorID = "D001", Status = "Open" });

原理说明

原代码报错是因为Action<T>(T为BaseEvent子类)无法直接转换为Action<BaseEvent>——Action委托是逆变类型,仅允许从基类委托转换为子类委托,反之则不成立。修改后的EventContainer通过泛型Add方法直接存储对应类型的Action<TEvent>,触发时再将Delegate转换为对应类型的委托调用,彻底避免了类型转换的困扰。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 15:10:34