是否可以从事件中取消订阅匿名方法?

如果我订阅了这样的活动:

void MyMethod()
{
    Console.WriteLine("I did it!");
}

MyEvent += MyMethod;

我可以像这样取消订阅:

MyEvent -= MyMethod;

但如果我使用匿名方式订阅:

MyEvent += delegate(){Console.WriteLine("I did it!");};

是否可以取消订阅此匿名方法?如果有,怎么做?


当前回答

我最近在一个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(而不是Subscribe)方法。

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;
        }
    }
}

这里的权衡是为每个事件提供一个实例会带来更多的开销,但是在我的场景中——这是一个可以接受的权衡,可以确保有效地收集垃圾,并且代码在订阅者端更易于维护。完整的例子。

其他回答

Action myDelegate = delegate(){Console.WriteLine("I did it!");};

MyEvent += myDelegate;


// .... later

MyEvent -= myDelegate;

只要保持对委托的引用。

我最近在一个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(而不是Subscribe)方法。

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;
        }
    }
}

这里的权衡是为每个事件提供一个实例会带来更多的开销,但是在我的场景中——这是一个可以接受的权衡,可以确保有效地收集垃圾,并且代码在订阅者端更易于维护。完整的例子。

如果最好的方法是在订阅的eventHandler上保持引用,则可以使用Dictionary来实现。

在本例中,我必须使用匿名方法为一组DataGridViews包含mergeColumn参数。

使用启用参数设置为true的MergeColumn方法启用事件,而使用设置为false的MergeColumn方法禁用事件。

static Dictionary<DataGridView, PaintEventHandler> subscriptions = new Dictionary<DataGridView, PaintEventHandler>();

public static void MergeColumns(this DataGridView dg, bool enable, params ColumnGroup[] mergedColumns) {

    if(enable) {
        subscriptions[dg] = (s, e) => Dg_Paint(s, e, mergedColumns);
        dg.Paint += subscriptions[dg];
    }
    else {
        if(subscriptions.ContainsKey(dg)) {
            dg.Paint -= subscriptions[dg];
            subscriptions.Remove(dg);
        }
    }
}

有点蹩脚的方法:

public class SomeClass
{
  private readonly IList<Action> _eventList = new List<Action>();

  ...

  public event Action OnDoSomething
  {
    add {
      _eventList.Add(value);
    }
    remove {
      _eventList.Remove(value);
    }
  }
}

重写事件添加/删除方法。 保留这些事件处理程序的列表。 当需要时,清除它们并重新添加其他的。

这可能行不通,也不是最有效的方法,但应该能把工作完成。

在3.0中可以缩短为:

MyHandler myDelegate = ()=>Console.WriteLine("I did it!");
MyEvent += myDelegate;
...
MyEvent -= myDelegate;