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

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

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

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

如果您希望能够控制取消订阅,那么您需要按照您接受的答案中指示的路线进行。但是,如果您只关心在订阅类超出作用域时清除引用,那么还有另一种解决方案(有点复杂),它涉及使用弱引用。我刚刚就这个话题发布了一个问答。

在3.0中可以缩短为:

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

如果你想用这个委托引用一些对象,你可以使用委托。创建elegate(类型,对象目标,MethodInfo MethodInfo) .net考虑委托equals by target和methodInfo