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

C#中使用逆变转换System.Action时出现错误的问题排查

委托类型不兼容问题:事件逆变转换失败的原因与解决办法

我定义了接口IShootAttackChild,其中包含System.Action<IShootAttackChild>类型的事件OnDestroy;同时有一个BulletController类,自身带有System.Action<BulletController>类型的同名事件OnDestroy。让BulletController实现接口时,我尝试直接复用类内已有的事件,代码在Unity 2022.3.4f1中编译正常,但运行时执行OnDestroy += value;或OnDestroy -= value;会抛出ArgumentException:委托类型不兼容。原本以为基于委托的逆变特性可以直接转换,而使用OnDestroy += value.Invoke;的写法却能正常运行。


相关代码示例

接口定义

public interface IShootAttackChild
{
    public event System.Action<IShootAttackChild> OnDestroy;
    /* 其他方法 */
}

初始类定义

public class BulletController
{
    public event System.Action<BulletController> OnDestroy;
}

编译正常但运行报错的实现代码

public class BulletController : IShootAttackChild
{
    public event System.Action<BulletController> OnDestroy;

    event System.Action<IShootAttackChild> IShootAttackChild.OnDestroy
    {
        add
        {
            OnDestroy += value;
        }
        remove
        {
            OnDestroy -= value;
        }
    }
}

可行的实现代码

public class BulletController : IShootAttackChild
{
    public event System.Action<BulletController> OnDestroy;

    event System.Action<IShootAttackChild> IShootAttackChild.OnDestroy
    {
        add
        {
            OnDestroy += value.Invoke;
        }
        remove
        {
            OnDestroy -= value.Invoke;
        }
    }
}

原因分析

  1. 逆变特性的适用范围误解:
    Action<T>的泛型参数是in T(逆变),允许将接受基类型参数的委托赋值给接受派生类型参数的委托变量(例如Action<IShootAttackChild>可以赋值给Action<BulletController>,因为BulletController实现了IShootAttackChild)。但这种转换仅适用于单个委托的赋值,不适用于多播委托的直接合并。

  2. 多播委托的类型严格匹配要求:
    事件本质是多播委托,CLR在合并或移除多播委托时,要求委托的类型必须完全一致。虽然编译时C#编译器允许逆变的隐式转换,但运行时CLR会检查委托的实际类型,发现Action<IShootAttackChild>与Action<BulletController>类型不匹配,因此抛出ArgumentException。

解决办法

通过创建一个包装委托,将Action<BulletController>的调用转发给Action<IShootAttackChild>的委托:

  • 使用value.Invoke作为包装逻辑,当类的OnDestroy事件触发时,会将BulletController实例传入这个包装委托,再调用value.Invoke(IShootAttackChild)(因为BulletController是IShootAttackChild的实现,类型兼容)。
  • 这种方式生成的包装委托类型是Action<BulletController>,与类的事件类型完全一致,满足CLR的多播委托合并要求,因此可以正常运行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 18:26:21