Unsubscribe anonymous method in C#
Asked Answered
M

14

256

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?

Magnanimity answered 8/10, 2008 at 15:24 Comment(1)
J
251
Action myDelegate = delegate(){Console.WriteLine("I did it!");};

MyEvent += myDelegate;


// .... later

MyEvent -= myDelegate;

Just keep a reference to the delegate around.

Jupiter answered 8/10, 2008 at 15:33 Comment(0)
C
151

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;
Cavie answered 8/10, 2008 at 15:34 Comment(4)
Using this kind of code, Resharper complains about accessing a modified closure... is this approach reliable? I mean, are we sure that the 'foo' variable inside the body of the anonymous method, really references the anonymous method itself?Median
I found an answer to my dubt, and it is that 'foo' will truly hold a reference to the anonymous method itslef. The captured variable is modified, since it is captured before the anonymous method get assigned to it.Median
That's exactly what I needed! I was missing the =null. (MyEventHandler foo = delegate {... MyEvent-=foo;}; MyEvent+=foo; didn't work...)Dank
Resharper 6.1 doesn't complain if you declare it as an array. Seems a little weird, but I'm going to blindly trust my tools on this one: MyEventHandler[] foo = { null }; foo[0] = ... { ... MyEvent -= foo[0]; }; MyEvent += foo[0];Indiscerptible
S
34

Since C# 7.0 local functions feature has been released, the approach suggested by J c becomes really neat.

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.

Scharaga answered 15/8, 2017 at 5:24 Comment(1)
To make the readability even better, you can move the MyEvent += foo; line to be before the declaration of foo.Lobito
B
25

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.

Boys answered 8/10, 2008 at 15:26 Comment(9)
I Jon, what do you meen ? I don't understand. does the solution exposed by "J c" will not work properly ?Giraudoux
@EricOuellet: That answer is basically an implementation of "retain the delegate somewhere else so you can unsubscribe with exactly the same delegate you used to subscribe".Boys
Jon, I'm sorry, I read your answer many times trying to figure out what you mean and where the "J c" solution does not use the same delegate to subscribe and unsubscribe, but I can't fint it. Pehaps you can point me on an article that explain what you are saying ? I know about your reputation and I would really like to understand what you mean, anything you can link to would be really appreciate.Giraudoux
I found : msdn.microsoft.com/en-us/library/ms366768.aspx but they do recommend not using anonymous but they do not say that there is any major problem ?Giraudoux
I found it... Thanks a lot (see Michael Blome answer): social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/…Giraudoux
@EricOuellet: The "J c" solution does use the same delegate. It puts it in a variable, and uses that variable for unsubscription. The "J c" solution also unsubscribes immediately that it's called, which is a little odd... but the bit of "store the delegate somewhere and use it in both places" is what I meant.Boys
If I understand what you are saying, I'm not agree. I think it is reasonable to unsubscribe from an event from the delegate itself. I had to do it in a TreeView see my comment in #1906495. I don't see any problem of storing the delegate twice while one ref will be destroy (in GC) as soon as method end. Do you suggest better implementation than mine (in link) ? ... I really think there is applicable place where we need to have one time delegate and the way I did it (link) appears to me to be very ok.Giraudoux
@EricOuellet: It's reasonable - it's just a little unusual, that's all - and not something that was asked in the question.Boys
Sorry, I just understand now what you meant. Thanks and sorry for my confusion. I misunderstood the answer/context.Giraudoux
U
17

In 3.0 can be shortened to:

MyHandler myDelegate = ()=>Console.WriteLine("I did it!");
MyEvent += myDelegate;
...
MyEvent -= myDelegate;
Uncommercial answered 23/7, 2009 at 1:53 Comment(0)
B
12

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):

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. For instance:

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

I've written a full post about this tecnique here.

Book answered 23/6, 2011 at 23:11 Comment(4)
Does this mean I could accidentally unsubscribe a different instance (i.e. not me) from the event if they subscribed after me?Intervenient
@Intervenient of course this would alaways deregister the last registered one. If you wanted to dynamically unsubscribe a specific anonymous delegate, you need to somehow identify it. I'd suggest keeping a reference then :)Haslet
Its pretty cool, what you are doing, but I can't imagine one case where this could be useful. But i't does really solve the OP's Question. --> +1. IMHO, one should simply not use anonymous delegates if they should be deregistered later. Keeping them is stupid --> better use Method. Removing just some delegate in Invocation list is pretty random and useless. Correct me if im wrong. :)Haslet
My favorite answer. Based on this I created an additional variant. (here)Selfconsequence
A
6

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.

Adult answered 8/10, 2008 at 15:29 Comment(1)
Even lame responses can have value to someone. In this case at least 6 people thought this approach was worthy of an upvote even though the author thought it lame.Negligence
D
2

One simple solution:

just pass the eventhandle variable as parameter to itself. 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!");
}
Dyedinthewool answered 12/7, 2014 at 14:11 Comment(1)
what if MyEvent is invoked twice, before MyEvent -= myEventHandlerInstance; is ran? If it's possible, you'd have an error. But im not sure if that's the case.Haslet
T
1

If the best way is to keep a reference on the subscribed eventHandler, this can be achieved using a Dictionary.

In this example, I have to use a anonymous method to include the mergeColumn parameter for a set of DataGridViews.

Using the MergeColumn method with the enable parameter set to true enables the event while using it with false disables it.

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);
        }
    }
}
Trigonometry answered 13/10, 2016 at 6:40 Comment(0)
W
0

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.

Winchester answered 17/11, 2009 at 8:5 Comment(0)
P
0

if you want refer to some object with this delegate, may be you can use Delegate.CreateDelegate(Type, Object target, MethodInfo methodInfo) .net consider the delegate equals by target and methodInfo

Principality answered 21/1, 2014 at 3:19 Comment(0)
R
0

There is a way to solve this by implementing the closure yourself instead of a lambda expression.

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.

var closure = new EventHandlerClosure
{
    a = a,
    b = b,
    c = c
};
var handler = new MyEventHandler(closure.MyMethod);
MyEvent += handler;
closure.Finished += (s, e)
{
    MyEvent -= handler;
}
Rhys answered 10/4, 2021 at 22:23 Comment(0)
N
0

I discovered this quite old thread recently for a C# project and found all the answers very useful. 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.

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. Full example here.

Nickname answered 12/7, 2021 at 1:12 Comment(0)
S
0

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.

Selfconsequence answered 28/7, 2022 at 10:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.