Can an anonymous delegate unsubscribe itself from an event once it has been fired?
Asked Answered
I

3

22

I'm wondering what the 'best practice' is, when asking an event handler to unsubscribe its self after firing once.

For context, this is my situation. A user is logged in, and is in a ready state to handle work items. They receive a work item, process it, then go back to ready again. At this point, they may want to say they're not available for more work items, but one is sent to them anyway. What I want to be able to do, is allow the user to 'queue' a 'I'm not available' as soon as that operation becomes possible.

public event SomeHandler StateChanged = delegate {};

public void QueueNotAvailable() 
{
    StateChanged += (s,e) => {
                                 if (e.CanGoNotAvailable) { 
                                     someObject.NotAvailable();
                                     StateChanged -= {thishandler};
                                 }
                             }
}

For my purposes, if a separate thread happens to fire off the events and this particular handler runs twice, it is not a problem. Requesting the same function very close together is acceptable. I just don't want it firing every single time the operation is allowed.

Ilocano answered 21/6, 2010 at 4:36 Comment(0)
P
47

You can save an instance of the delegate before subscribing to the event:

public void QueueNotAvailable() 
{
    SomeHandler handler = null;
    handler = (s,e) {
        // ...
        StateChanged -= handler;
    };
    StateChanged += handler;
}

I believe this should do it... I had to put the initial handler = null in there otherwise you get 'Use of unassigned local variable' compilation errors, but I think it's actually benign.

Phylloid answered 21/6, 2010 at 4:50 Comment(6)
That's pretty cool that it can reference itself. Surely what you're saying in there is "StateChanged -= null" ? You'd also have to have the handler stored in a class-level member as I assume the OP wants to change the StateChanged/handler state each handler execution.Obstruent
@Graphain: I meant on line 3, I have SomeHandler handler = null; - I had to put that on a line on it's own (rather than assigning it directly to the anonymous delegate) because I got a compiler error otherwise. It doesn't need to be a class-level member, since the closure created by the anonymous delegate will just do it's magic...Phylloid
i realise that, but i mean on line 5 (ignoring comment), handler would exist as null when it creates it. I guess we're lucky in that the code isn't executed until handler is bound so it's a non issue. Like I mean "a=null" then "a=(a+1)" is actually "(a=(null+1)"Obstruent
@Graphain: Right, but I think the compiler is wrong to give the "use of unassigned local variable" in this particular case, because it's physically impossible (AFAICT) to call the anonymous delegate without first assigning it to the variable. So it's not possible that handler will ever be unassigned inside the delegate body. Anyway, it's only one extra line so not a big deal :)Phylloid
Fantastic! I was thinking that I'd have to store the SomeHandler reference in a higher scope to get it to work, and 15 or so similar functions would look messy. Forgot about the closure. ThanksIlocano
The above code fails on StateChanged -= handler with the error: Use of unassigned local variable 'handler'Etesian
B
8

Starting with C# 7.0, C# supports local functions.

Local functions are private methods of a type that are nested in another member. They can only be called from their containing member.

public void QueueNotAvailable() 
{
    void handler(object sender, EventArgs e)
    {
        // do something
        StateChanged -= handler;
    }
    StateChanged += handler;
}
Brucite answered 19/11, 2018 at 9:46 Comment(1)
a very minor semantic difference between this and the current top answer: this will allocate a second delegate object/instance for the unsubscribe; in most cases, this won't be noticed, of courseBattik
M
0

The approach, which Dean Harding suggests works like a charm, but you need to add 2 lines per event and need a name per each handler which makes code messier. To avoid this, you can write some smart delegate like the following:

public delegate void SmartAction<T>(SmartAction<T> self, T data);

public event SmartAction<int> ProgressChanged;

ProgressChanged += (self, progress) => 
{ 
  Console.WriteLine(progress);
  ProgressChanged -= self;
};
Mcmaster answered 19/7, 2018 at 22:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.