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

Unity下C#静态Action委托订阅与自动取消订阅实现方案咨询

解决Unity中Action订阅自动取消的重复代码问题

问题背景

我们基于Unity开发,项目里有一个包含静态Action的Listeners类,业务代码会在各处订阅这些事件:

class Listeners{
    public static Action OnGameLoaded;
    public static Action OnAfterGameLoaded;
}

原本每个需要订阅的类(比如Monster)都要手动编写重复代码:先取消旧订阅(避免重复绑定)、添加新订阅,还要在对象销毁时手动取消订阅,流程繁琐且容易遗漏:

class Monster : MonoBehaviour {

    // Awake在对象创建时调用
    public override void Awake(){
      
        Listeners.OnGameLoaded -= OnGameLoaded; // 先取消防止重复订阅
        Listeners.OnGameLoaded += OnGameLoaded;
    }

    // OnDestroy在对象销毁时调用
    public override void OnDestroy(){
        Listeners.OnGameLoaded -= OnGameLoaded;
    }

    private void OnGameLoaded(){
        // 业务逻辑实现
    }

}

我们希望简化流程:只需要调用一个Subscribe方法完成订阅,无需手动处理OnDestroy中的取消逻辑,理想用法如下:

class Monster : SomeSuperclass {
    
    public override void Awake(){
        Subscribe(Listeners.OnGameLoaded, OnGameLoaded);
    }
    
    private void OnGameLoaded(){
        // 业务逻辑实现
    }

    // 无需再编写OnDestroy代码
}

最初尝试编写了一个基类来封装订阅逻辑,但调试发现:由于Action是值类型,传递时会被复制,导致基类存储的Action实例和Listeners类中的静态Action实例不一致,最终无法完成有效取消订阅。原基类代码如下:

class BaseClass : MonoBehaviour
{
    private Dictionary<Action, Action> actions = new Dictionary<Action, Action>();

    protected void Subscribe(ref Action action, Action function)
    {
        action += function;
        actions.Add(action, function);
    }

    public virtual void OnDestroy()
    {
        // 此处操作的是旧的Action实例,无法影响静态类中的当前Action
        foreach(KeyValuePair<Action, Action> entry in actions)
        {
            Action action = entry.Key;
            action -= entry.Value;
        }
    }
}

解决方案

核心问题是不能存储Action值本身,而是要存储对原始静态Action的引用,或者直接存储“取消订阅”的操作逻辑,在对象销毁时执行。以下提供两种可行实现:

方式一:存储取消订阅操作(简洁版)

修改基类,不再存储Action实例,而是存储一个封装了取消订阅逻辑的委托,在OnDestroy时执行这些委托:

class BaseSubscriber : MonoBehaviour
{
    private List<Action> _unsubscribers = new List<Action>();

    protected void Subscribe(ref Action sourceAction, Action handler)
    {
        // 先取消再订阅,避免重复绑定
        sourceAction -= handler;
        sourceAction += handler;

        // 存储取消订阅的操作
        _unsubscribers.Add(() => sourceAction -= handler);
    }

    protected virtual void OnDestroy()
    {
        foreach (var unsubscribe in _unsubscribers)
        {
            unsubscribe();
        }
        _unsubscribers.Clear();
    }
}

使用时直接继承该基类,调用Subscribe即可:

class Monster : BaseSubscriber {
    
    public override void Awake(){
        Subscribe(ref Listeners.OnGameLoaded, OnGameLoaded);
    }
    
    private void OnGameLoaded(){
        // 业务逻辑实现
    }
}

方式二:结合WeakReference(防内存泄漏版)

如果担心静态Action持有MonoBehaviour实例导致内存泄漏(比如对象已销毁但订阅未取消),可以用WeakReference包装实例,实现自动清理:

class SafeBaseSubscriber : MonoBehaviour
{
    private List<Action> _unsubscribers = new List<Action>();

    // 针对无参数Action的重载
    protected void Subscribe(ref Action sourceAction, Action handler)
    {
        WeakReference<MonoBehaviour> weakRef = new WeakReference<MonoBehaviour>(this);
        
        Action wrappedHandler = () =>
        {
            if (weakRef.TryGetTarget(out MonoBehaviour instance))
            {
                handler();
            }
            else
            {
                // 实例已销毁,自动从静态Action中移除
                sourceAction -= wrappedHandler;
            }
        };

        sourceAction -= wrappedHandler;
        sourceAction += wrappedHandler;

        _unsubscribers.Add(() => sourceAction -= wrappedHandler);
    }

    // 针对带参数Action的重载(可选)
    protected void Subscribe<T>(ref Action<T> sourceAction, Action<T> handler) where T : MonoBehaviour
    {
        WeakReference<T> weakRef = new WeakReference<T>(this as T);
        
        Action<T> wrappedHandler = (param) =>
        {
            if (weakRef.TryGetTarget(out T instance))
            {
                handler(param);
            }
            else
            {
                sourceAction -= wrappedHandler;
            }
        };

        sourceAction -= wrappedHandler;
        sourceAction += wrappedHandler;

        _unsubscribers.Add(() => sourceAction -= wrappedHandler);
    }

    protected virtual void OnDestroy()
    {
        foreach (var unsubscribe in _unsubscribers)
        {
            unsubscribe();
        }
        _unsubscribers.Clear();
    }
}

原代码无效的原因

Action本质是值类型委托,每次执行+=或-=都会生成一个新的Action实例。原基类中把修改后的Action实例存入字典,但Listeners类中的静态Action已经指向了新的实例,销毁时操作的是字典里的旧实例,自然无法影响到静态类中的当前Action,导致取消订阅失效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 14:43:16