简体   繁体   English

取消订阅 C# 中的匿名方法

[英]Unsubscribe anonymous method in C#

Is it possible to unsubscribe an anonymous method from an event?是否可以从事件中取消订阅匿名方法?

If I subscribe to an event like this:如果我订阅这样的事件:

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

MyEvent += MyMethod;

I can un-subscribe like this:我可以像这样取消订阅:

MyEvent -= MyMethod;

But if I subscribe using an anonymous method:但是,如果我使用匿名方法订阅:

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

is it possible to unsubscribe this anonymous method?是否可以取消订阅此匿名方法? If so, how?如果是这样,怎么做?

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

MyEvent += myDelegate;


// .... later

MyEvent -= myDelegate;

Just keep a reference to the delegate around.只需保留对委托的引用即可。

One technique is to declare a variable to hold the anonymous method which would then be available inside the anonymous method itself.一种技术是声明一个变量来保存匿名方法,然后该变量将在匿名方法本身内部可用。 This worked for me because the desired behavior was to unsubscribe after the event was handled.这对我有用,因为所需的行为是在处理事件后取消订阅。

Example:例子:

MyEventHandler foo = null;
foo = delegate(object s, MyEventArgs ev)
    {
        Console.WriteLine("I did it!");
        MyEvent -= foo;
    };
MyEvent += foo;

Since C# 7.0 local functions feature has been released, the approach suggested by J c becomes really neat.由于C# 7.0 本地函数特性已经发布, J c建议的方法变得非常简洁。

void foo(object s, MyEventArgs ev)
{
    Console.WriteLine("I did it!");
    MyEvent -= foo;
};
MyEvent += foo;

So, honestly, you do not have an anonymous function as a variable here.所以,老实说,这里没有匿名函数作为变量。 But I suppose the motivation to use it in your case can be applied to local functions.但我认为在您的情况下使用它的动机可以应用于本地功能。

From memory, the specification explicitly doesn't guarantee the behaviour either way when it comes to equivalence of delegates created with anonymous methods.从记忆中,当涉及到使用匿名方法创建的委托的等效性时,规范明确不保证任何方式的行为。

If you need to unsubscribe, you should either use a "normal" method or retain the delegate somewhere else so you can unsubscribe with exactly the same delegate you used to subscribe.如果您需要取消订阅,您应该使用“正常”方法或将委托保留在其他地方,以便您可以使用与订阅时完全相同的委托来取消订阅。

In 3.0 can be shortened to:在 3.0 中可以缩短为:

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

Instead of keeping a reference to any delegate you can instrument your class in order to give the event's invocation list back to the caller.您可以检测您的类,以便将事件的调用列表返回给调用者,而不是保留对任何委托的引用。 Basically you can write something like this (assuming that MyEvent is declared inside MyClass):基本上你可以写这样的东西(假设 MyEvent 在 MyClass 中声明):

public class MyClass 
{
  public event EventHandler MyEvent;

  public IEnumerable<EventHandler> GetMyEventHandlers()  
  {  
      return from d in MyEvent.GetInvocationList()  
             select (EventHandler)d;  
  }  
}

So you can access the whole invocation list from outside MyClass and unsubscribe any handler you want.因此,您可以从 MyClass 外部访问整个调用列表并取消订阅您想要的任何处理程序。 For instance:例如:

myClass.MyEvent -= myClass.GetMyEventHandlers().Last();

I've written a full post about this tecnique here .我写了一个完整的帖子关于这个tecnique 这里

Kind of lame approach:一种蹩脚的方法:

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

  ...

  public event Action OnDoSomething
  {
    add {
      _eventList.Add(value);
    }
    remove {
      _eventList.Remove(value);
    }
  }
}
  1. Override the event add/remove methods.覆盖事件添加/删除方法。
  2. Keep a list of those event handlers.保留这些事件处理程序的列表。
  3. When needed, clear them all and re-add the others.需要时,将它们全部清除并重新添加其他。

This may not work or be the most efficient method, but should get the job done.这可能不起作用或不是最有效的方法,但应该可以完成工作。

If you want to be able to control unsubscription then you need to go the route indicated in your accepted answer.如果您希望能够控制取消订阅,那么您需要按照您接受的答案中指示的路线进行。 However, if you are just concerned about clearing up references when your subscribing class goes out of scope, then there is another (slightly convoluted) solution which involves using weak references.但是,如果您只关心在订阅类超出范围时清除引用,那么还有另一种(稍微复杂的)解决方案涉及使用弱引用。 I've just posted a question and answer on this topic.我刚刚发布了有关此主题的问答

One simple solution:一个简单的解决方案:

just pass the eventhandle variable as parameter to itself.只需将 eventhandle 变量作为参数传递给自身。 Event if you have the case that you cannot access the original created variable because of multithreading, you can use this:如果您遇到由于多线程而无法访问原始创建变量的情况,则可以使用以下方法:

MyEventHandler foo = null;
foo = (s, ev, mehi) => MyMethod(s, ev, foo);
MyEvent += foo;

void MyMethod(object s, MyEventArgs ev, MyEventHandler myEventHandlerInstance)
{
    MyEvent -= myEventHandlerInstance;
    Console.WriteLine("I did it!");
}

如果你想用这个委托引用某个对象,可能你可以使用 Delegate.CreateDelegate(Type, Object target, MethodInfo methodInfo) .net 考虑委托等于 target 和 methodInfo

If the best way is to keep a reference on the subscribed eventHandler, this can be achieved using a Dictionary.如果最好的方法是保留对订阅的 eventHandler 的引用,则可以使用 Dictionary 来实现。

In this example, I have to use a anonymous method to include the mergeColumn parameter for a set of DataGridViews.在此示例中,我必须使用匿名方法为一组 DataGridView 包含 mergeColumn 参数。

Using the MergeColumn method with the enable parameter set to true enables the event while using it with false disables it.使用 MergeColumn 方法并将 enable 参数设置为 true 会启用该事件,而将其与 false 一起使用则会禁用它。

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

There is a way to solve this by implementing the closure yourself instead of a lambda expression.有一种方法可以通过自己实现闭包而不是 lambda 表达式来解决这个问题。

Assume that the class to be used as a capture variable is as follows.假设用作捕获变量的类如下。

public class A
{
    public void DoSomething()
    {
        ...
    }
}

public class B
{
    public void DoSomething()
    {
        ...
    }
}

public class C
{
    public void DoSomething()
    {
        ...
    }
}

These classes will be used as capture variables, so we instantiate them.这些类将用作捕获变量,因此我们将它们实例化。

A a = new A();
B b = new B();
C c = new C();

Implement the closure class as shown below.实现闭包类,如下所示。

private class EventHandlerClosure
{
    public A a;
    public B b;
    public C c;

    public event EventHandler Finished;

    public void MyMethod(object, MyEventArgs args)
    {
        a.DoSomething();
        b.DoSomething();
        c.DoSomething();
        Console.WriteLine("I did it!");

        Finished?.Invoke(this, EventArgs.Empty);
    }
}

Instantiate the closure class, create a handler, then subscribe to the event and subscribe to the lambda expression that unsubscribes from the closure class's Finished event.实例化闭包类,创建一个处理程序,然后订阅事件并订阅从闭包类的 Finished 事件取消订阅的 lambda 表达式。

var closure = new EventHandlerClosure
{
    a = a,
    b = b,
    c = c
};
var handler = new MyEventHandler(closure.MyMethod);
MyEvent += handler;
closure.Finished += (s, e)
{
    MyEvent -= handler;
}

I discovered this quite old thread recently for a C# project and found all the answers very useful.我最近为一个 C# 项目发现了这个相当古老的线程,并发现所有答案都非常有用。 However, there was one aspect that didn't work well for my particular use case - they all put the burden of unsubscribing from an event on the subscriber.但是,对于我的特定用例,有一个方面效果不佳 - 它们都将取消订阅事件的负担推给了订阅者。 I understand that one could make the argument that it's the subscribers job to handle this, however that isn't realistic for my project.我知道有人可能会说这是订阅者的工作来处理这个问题,但这对我的项目来说是不现实的。

My primary use case for events is for listening to timers to sequence animations (it's a game).我的事件的主要用例是监听定时器来对动画进行排序(这是一个游戏)。 In this scenario, I use a lot of anonymous delegates to chain together sequences.在这种情况下,我使用了很多匿名委托来将序列链接在一​​起。 Storing a reference to these isn't very practical.存储对这些的引用不是很实用。

In order to solve this, I've created a wrapper class around an event that lets you subscribe for a single invocation.为了解决这个问题,我围绕一个事件创建了一个包装类,让您订阅单个调用。

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

The side benefit of having this in a class is that you can make it private and expose only the functionality you want.在类中使用它的附带好处是您可以将其设为私有并仅公开您想要的功能。 For example, only expose the SubscribeOnce (and not the Subscribe) method.例如,只公开 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;
        }
    }
}

The tradeoff here is more overhead for having an instance of this for each event, however in my scenario - this is an acceptable tradeoff to ensure that garbage gets collected efficiently and the code is more maintainable on the subscriber side.这里的权衡是为每个事件拥有一个 this 实例的更多开销,但是在我的场景中 - 这是一个可以接受的权衡,以确保垃圾被有效收集并且代码在订阅者方面更易于维护。 Full example here .完整示例在这里

Here is a simple solution, which removes all assigned methods from an event.这是一个简单的解决方案,它从事件中删除所有分配的方法 Also anonymous methods.也是匿名方法。

Use this code and adjust the names.使用此代码并调整名称。

if (MyEvent != null)
    foreach (Delegate del in MyEvent.GetInvocationList())
        MyEvent -= (EventHandler<MyEventHandlerType>)del;

Example usage示例用法

public class SomeClass
{
  public event EventHandler<NiceEventArgs> NiceEvent;

  public void RemoveHandlers()
  {
    if (NiceEvent != null)
      foreach (Delegate del in NiceEvent.GetInvocationList())
        NiceEvent -= (EventHandler<NiceEventArgs>)del;
  }
}

Thanks to hemme's answer , which I used as inspiration.感谢hemme 的回答,我以此为灵感。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM