从 C# 中的事件注销委托时内存分配较高

High memory allocations when unregistering delegates from event in C#

我们正在使用 Unity3D 引擎开发游戏(用户代码使用 Mono - 我们的代码是用 C# 编写的)。

场景是我们有一个 class 公开一个事件,该事件有大约 250 个注册(游戏地图上的每个关卡对象都将自己注册到该事件):

// Every level registers itself (around 250 levels)
ScoresDataHelper.OnScoresUpdate += HandleOnScoresUpdate;

当销毁这个场景时,每个对象都会从事件中注销自己:

ScoresDataHelper.OnScoresUpdate -= HandleOnScoresUpdate;

使用内置分析器时,我看到大量内存分配,深入挖掘表明这是由于委托未注册所致。

我怀疑这是因为委托是不可变的,当将它们链接在一起时,会创建新的实例?

这是 Unity 分析器的屏幕截图:

在处理大量事件订阅时,有什么方法可以避免这些内存分配吗?

一个简单的解决方案是首先不使用事件。

// T,S,U is whatever your function takes and returns
private List<Func<T,S,U>> Listeners = new List<Func<T,S,U>>();
public void OnScoresUpdate(Func<T,S,U> listener){
    Listeners.Add(listener);
}

// when you want to fire the event
foreach(var listener in Listeners){
    listener(param1, param2);
}

// when you want to unsubscribe the listeners:
Listeners = new List<Func<T,S,U>>();

如果您想避免内存问题,您也可以使用弱收集,一旦元素被垃圾收集,就会自动删除侦听器。

正如您在中确认要退订所有活动订阅,没有理由一一退订。

您可以将事件设置为空。这将在不分配任何内存的情况下取消订阅事件的所有内容。

this.OnScoresUpdate = null;

需要注意的一件事是,您不能从 ScoresDataHelper class 外部执行此操作。它必须在 ScoresDataHelper class.

如果您想取消订阅特定事件,您可以将集合更改为 HashSet。然后,您将删除一个侦听器,例如:

private HashSet<Func<T,S,U>> listeners = new HashSet<Func<T,S,U>>();

public void RemoveListener(Func<T,S,U> listener){listeners.remove(listener);}

添加此答案是因为这是 Google 目前的最高结果:

根据 these tests,将函数添加到委托会产生 208 字节的垃圾,将委托添加到 event 会产生 104 字节乘以添加的事件数。因此,添加到事件的第 10 个委托会产生 1kb 的垃圾!我想象一个类似的取消订阅测试会产生相同的结果。使用 ListHashSet 实现自定义事件 class 的两个答案对于我自己在 Unity 中的此问题版本来说是一个简单有效的解决方案。