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

如何在泛型逆变IHandler接口上正确调用Invoke方法?

问题分析与解决方案

这个问题我之前也碰到过,核心是泛型接口的逆变特性和dynamic绑定的细节问题,咱们一步步拆解解决:

先说说两次尝试失败的原因

第一次强制转换失败

你的IHandler<in T>是逆变泛型接口,逆变的规则是:如果Derived继承自Base,你可以把IHandler<Base>转换为IHandler<Derived>,但反过来不行。你这里试图把IHandler<ChatEvent>(泛型参数是子类ChatEvent)强转为IHandler<IEvent>(泛型参数是基类IEvent),这是协变的方向,逆变不支持这种转换,所以抛出InvalidCastException。

第二次dynamic绑定失败

虽然tl的实际类型是MyHandler<ChatEvent>,ev的实际类型也是ChatEvent,但你传入的ev是IEvent类型的变量。dynamic会按照编译时类型(也就是IEvent)去匹配Invoke方法,而MyHandler<ChatEvent>.Invoke只接受ChatEvent类型的参数,所以运行时绑定器找不到匹配的方法,抛出RuntimeBinderException。


推荐解决方案(按优先级排序)

方案一:给IHandler添加非泛型Invoke方法(最推荐,类型安全+高性能)

修改基础接口,新增一个非泛型的Invoke方法,让泛型子类实现它:

interface IHandler
{
    Type Type { get; }
    // 新增非泛型Invoke方法
    void Invoke(IEvent ev);
}

interface IHandler<in T> : IHandler where T : IEvent
{
    void Invoke(T ev);
}

// 实现类中显式实现非泛型接口逻辑
class MyHandler<T> : IHandler<T> where T : IEvent
{
    public Type Type => typeof(T);

    // 泛型Invoke的业务逻辑
    public void Invoke(T ev)
    {
        // 你的事件处理代码
    }

    // 非泛型Invoke的适配逻辑(因为有Type断言,转换绝对安全)
    void IHandler.Invoke(IEvent ev)
    {
        Invoke((T)ev);
    }
}

然后你的调用代码就可以直接调用非泛型方法:

public void Invoke(IEvent ev)
{
    ImmutableArray<IHandler> selected;
    lock (_listeners)
    {
        selected = GetHandlersFor(ev.GetType()).ToImmutableArray();
    }

    foreach (var tl in selected)
    {
        Debug.Assert(tl.Type.IsInstanceOfType(ev));
        tl.Invoke(ev); // 直接调用,类型安全且无额外开销
    }
}

这个方案是最优解,既保证了类型安全,又没有反射或dynamic带来的性能损耗,代码可读性也最高。

方案二:用反射调用泛型Invoke方法

如果不想修改原有接口,可以用反射来获取并调用目标方法:

public void Invoke(IEvent ev)
{
    ImmutableArray<IHandler> selected;
    lock (_listeners)
    {
        selected = GetHandlersFor(ev.GetType()).ToImmutableArray();
    }

    foreach (var tl in selected)
    {
        Debug.Assert(tl.Type.IsInstanceOfType(ev));
        // 获取泛型Invoke方法并调用
        var invokeMethod = tl.GetType().GetMethod(nameof(IHandler<IEvent>.Invoke));
        invokeMethod?.Invoke(tl, new[] { ev });
    }
}

注意:反射有一定性能开销,如果是高频调用场景,建议提前缓存每个IHandler类型的Invoke方法,减少反射次数。

方案三:修正dynamic的调用方式

如果坚持用dynamic,需要先把ev转换为tl.Type对应的类型,让dynamic能正确匹配方法:

public void Invoke(IEvent ev)
{
    ImmutableArray<IHandler> selected;
    lock (_listeners)
    {
        selected = GetHandlersFor(ev.GetType()).ToImmutableArray();
    }

    foreach (var tl in selected)
    {
        Debug.Assert(tl.Type.IsInstanceOfType(ev));
        // 把ev转换为处理器对应的事件类型
        dynamic typedEv = Convert.ChangeType(ev, tl.Type);
        ((dynamic)tl).Invoke(typedEv);
    }
}

这个方案依赖Convert.ChangeType,你的Debug.Assert已经保证了类型兼容,所以转换是安全的,但dynamic仍有一定性能开销,可读性也不如方案一。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 06:40:39