5

If I create a lambda callback such as this:

var someInstance = new SomeObject();

someInstance.Finished += (obj, args) =>
{
      // Do something

      // Deregister here
};

someInstance.DoAction();

How could I deregister the callback as part of the actual handler code? This pattern would be ideal as I could ensure it is released as soon as possible.

I've seen similar questions but not one where this type of example is directly addressed.

Keith Adler
  • 20,880
  • 28
  • 119
  • 189
  • Very similar to http://stackoverflow.com/questions/183367/unsubscribe-anonymous-method-in-c – StriplingWarrior Apr 26 '11 at 22:51
  • Similar yes, but what I am looking for is a way to deregister within the actual handler and not in some other scope. – Keith Adler Apr 26 '11 at 22:51
  • 1
    You might find this question I asked interesting: http://stackoverflow.com/questions/5623658/single-shot-event-subscription – spender Apr 26 '11 at 23:00
  • Hello Nissan Fan There are several things you can do it some are better then others but the real issue is thread management, obviously if you know the calling code is not multi-threaded but if it is you should make the code making this kinda call is thread safe to ensure there is no clashes. Delegates are and threads tend to need careful considerations but of course there is no need to over think it. – dmportella Apr 26 '11 at 23:09
  • This may lead to a follow-up which involves mutexes. It just seems like .NET could benefit from having a way to self-dereference these lambdas. – Keith Adler Apr 26 '11 at 23:12
  • @Nissan Fan: you **can** self-dereference, see my answer – quentin-starin Apr 26 '11 at 23:14
  • @qes: I'm sure @Nissan Fan means a way built into the language that doesn't involve reflection. – StriplingWarrior Apr 26 '11 at 23:18
  • @StiplingWarrior: Really? Where does he say that? Seems like an assumption on your part, and why would it matter if it is built into the language or not if you can achieve what you want? – quentin-starin Apr 26 '11 at 23:20
  • I've seen it too, based my answer on one, but I couldn't find it with a quick lookup. Someone had posted it as a Fire Once event, but I couldn't find it to link it. – Chuck Savage Apr 26 '11 at 23:23
  • @qes: You're right, I was reading between the lines. Since you had answered his question with a way that uses reflection, and he said "It just seems like .NET could benefit from having a way to self-dereference these lambdas," I guess I sort of figured he probably figured .NET could benefit from a way to self-dereference lambdas which doesn't use reflection. I meant no offense by it. – StriplingWarrior Apr 26 '11 at 23:34

5 Answers5

6

With something like,

var someInstance = new SomeObject();

EventHandler myDelegate = null;
myDelegate = (obj, args) =>
{
      // Do something

      // Deregister here
      someInstance.Finished -= myDelegate;
};
someInstance.Finished += myDelegate;

someInstance.DoAction();
Chuck Savage
  • 11,775
  • 6
  • 49
  • 69
5

If you want the ability to unregister, then it's best to define your lambda as an Action, so that you can write:

someInstance.Finished += MyCustomAction;

and later

someInstance.Finished -= MyCustomAction;
Roy Dictus
  • 32,551
  • 8
  • 60
  • 76
5

You can use MethodInfo.GetCurrentMethod inside your lambda to retrieve the MethodInfo of the lambda.

With the MethodInfo, you can use Delegate.CreateDelegate to get the properly typed delegate representing your lambda.

And with the delegate, you can unregister the lambda, all without storing your function in a variable or making it a named method.

class MyClass 
{
    public event EventHandler TheEvent;

    void TestIt()
    {
        TheEvent += (sender, eventargs) => 
            {
            Console.WriteLine("Handled!"); // do something in the handler

            // get a delegate representing this anonymous function we are in
            var fn = (EventHandler)Delegate.CreateDelegate(
                             typeof(EventHandler), sender, 
                             (MethodInfo)MethodInfo.GetCurrentMethod());

            // unregister this lambda when it is run
            TheEvent -= fn;
        };


        // first time around this will output a line to the console
        TheEvent(this, EventArgs.Empty);

        // second time around there are no handlers attached and it will throw a NullReferenceException
        TheEvent(this, EventArgs.Empty);
    }

}
quentin-starin
  • 26,121
  • 7
  • 68
  • 86
  • 1
    I really like this code example. It's a shame that MS doesn't expand the C# language to include a release event keyword that automatically unbinds the event handler for the currently scoped code. – Keith Adler Apr 27 '11 at 04:36
  • Hrm .. I'm not seeing an obvious way to put the implementation in its own method either due to the limitations against using delegate types as generic type constraints. – quentin-starin Apr 27 '11 at 06:00
1

If you want to remove the event handler afterwards, you should use a named function:

  someInstance.Finished += HandleFinished;

  //...
  void HandleFinished(object sender, EventArgs args) 
  {  
    someInstance.Finished -= HandleFinished;
  }
Mark Cidade
  • 98,437
  • 31
  • 224
  • 236
  • So there is no way to deregister the callback from within the actual handler as part of a clean-up process? – Keith Adler Apr 26 '11 at 22:49
  • @Nissan Fan: I believe it is possible to get a delegate from a MethodBase (i.e., `MethodInfo.GetCurrentMethod`), but I don't have the time at this moment to find out exactly how ... if I remember I'll come back to it but I would say don't give up too quickly. – quentin-starin Apr 26 '11 at 22:57
1

This appears to work:

EventHandler handler = null;
handler = new EventHandler((sender, e) => SomethingHappened -= handler);
SomethingHappened += handler;

Just be sure you don't assign handler to any other value, as the closure is tied to that variable. This is what Resharper complains of as an "Access to Modified Closure."

StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315