使用UnityAction或C# Action时最优避免内存泄漏及简化注册的方法
问题原因
你封装失败的核心原因有两点:
- C#中的委托属于值类型,直接将
UnityAction作为值参数传入函数时,函数内拿到的是原事件委托的副本,对副本执行+=操作不会修改外部的原始事件MyEvents.SomeEvent,注册自然不会生效 - 代码存在笔误:参数定义名为
_thingToAdd,但注销逻辑中使用了不存在的变量_delegateToAdd,本身就会触发编译错误
另外你提到的HandleSomeEvent本质是方法组,C#会自动将其隐式转换为匹配签名的UnityAction委托实例,属于持有方法引用的委托对象,不是字符串或其他类型。
可行解决方案
方案1:通用全兼容方案(推荐)
如果你的事件是用标准event关键字修饰的(符合C#事件规范),可以用lambda包裹注册/注销逻辑的方式实现封装,不需要修改原有事件定义,适配所有委托类型:
// 直接用C#内置的Action存所有注销逻辑,不需要自定义Dreg委托 private Action deRegisterAll; // 通用注册方法,分别传入注册逻辑和注销逻辑 public void RegisterEvent(Action registerLogic, Action unregisterLogic) { registerLogic.Invoke(); deRegisterAll += unregisterLogic; }
调用时一行完成注册:
// 调用示例,无额外学习成本 RegisterEvent( () => MyEvents.SomeEvent += HandleSomeEvent, () => MyEvents.SomeEvent -= HandleSomeEvent );
在脚本销毁/程序退出时统一注销:
private void OnDestroy() { deRegisterAll?.Invoke(); deRegisterAll = null; // 清空引用避免残留 }
该方案优势:
- 兼容无参数
UnityAction、带参数UnityAction<T>、普通C#Action、标准event等所有委托类型,不需要写多个重载 - 无反射开销,性能和你手写的原始方案完全一致
方案2:ref参数简化调用
如果你的事件没有加event关键字修饰(直接定义为public static UnityAction SomeEvent),可以用ref参数实现更简洁的调用:
private Action deRegisterAll; // 无参数UnityAction重载 public void RegisterEvent(ref UnityAction targetEvent, UnityAction handler) { targetEvent += handler; deRegisterAll += () => targetEvent -= handler; } // 带1个参数的UnityAction<T>重载,有更多参数需求可以继续加重载 public void RegisterEvent<T>(ref UnityAction<T> targetEvent, UnityAction<T> handler) { targetEvent += handler; deRegisterAll += () => targetEvent -= handler; }
调用方式:
RegisterEvent(ref MyEvents.SomeEvent, HandleSomeEvent);
内容的提问来源于stack exchange,提问作者Rachmaninow
相关产品推荐
相关产品推荐

