我最近为一个 C# 项目发现了这个相当古老的线程,并发现所有答案都非常有用。但是,有一个方面不适用于我的特定用例 - 它们都将取消订阅事件的负担放在了订阅者身上。我知道有人可能会说这是订户的工作来处理这个问题,但这对我的项目来说是不现实的。
我对事件的主要用例是监听计时器以对动画进行排序(这是一个游戏)。在这种情况下,我使用大量匿名委托将序列链接在一起。存储对这些的引用不是很实用。
为了解决这个问题,我围绕一个事件创建了一个包装类,让您可以订阅单个调用。
internal class EventWrapper<TEventArgs> {
private event EventHandler<TEventArgs> Event;
private readonly HashSet<EventHandler<TEventArgs>> _subscribeOnces;
internal EventWrapper() {
_subscribeOnces = new HashSet<EventHandler<TEventArgs>>();
}
internal void Subscribe(EventHandler<TEventArgs> eventHandler) {
Event += eventHandler;
}
internal void SubscribeOnce(EventHandler<TEventArgs> eventHandler) {
_subscribeOnces.Add(eventHandler);
Event += eventHandler;
}
internal void Unsubscribe(EventHandler<TEventArgs> eventHandler) {
Event -= eventHandler;
}
internal void UnsubscribeAll() {
foreach (EventHandler<TEventArgs> eventHandler in Event?.GetInvocationList()) {
Event -= eventHandler;
}
}
internal void Invoke(Object sender, TEventArgs e) {
Event?.Invoke(sender, e);
if(_subscribeOnces.Count > 0) {
foreach (EventHandler<TEventArgs> eventHandler in _subscribeOnces) {
Event -= eventHandler;
}
_subscribeOnces.Clear();
}
}
internal void Remove() {
UnsubscribeAll();
_subscribeOnces.Clear();
}
}
在类中使用它的另一个好处是您可以将其设为私有并仅公开您想要的功能。例如,仅公开 SubscribeOnce(而不是订阅)方法。
public class MyClass {
private EventWrapper<MyEventEventArgs> myEvent = new EventWrapper<MyEventEventArgs>();
public void FireMyEvent() {
myEvent.Invoke(this, new MyEventEventArgs(1000, DateTime.Now));
}
public void SubscribeOnce(EventHandler<MyEventEventArgs> eventHandler) {
myEvent.SubscribeOnce(eventHandler);
}
public class MyEventEventArgs : EventArgs {
public int MyInt;
public DateTime MyDateTime;
public MyEventEventArgs(int myInt, DateTime myDateTime) {
MyInt = myInt;
MyDateTime = myDateTime;
}
}
}
这里的权衡是为每个事件创建一个实例的开销更大,但是在我的场景中 - 这是一个可接受的权衡,以确保有效地收集垃圾并且代码在订阅者端更易于维护。 完整的例子在这里。