In C# 5, what is the behavior of the -= operator when unsubscribing from events.
Assume subscribing to the same event multiple times is valid for this application logic, such as follows:
Property_Saved += Property_Saved_Handler;
Property_Saved += Property_Saved_Handler;
Property_Saved += Property_Saved_Handler;
Now we are subscribed three times.
After unsubscribing with the following one line of code:
Property_Saved -= Property_Saved_Handler;
How many subscriptions are left? 2? none? ...?
Two are left after that. Each -= only removes one subscription. At least, that's the case if it's using just a regular delegate to back the event.
You can see this easily without really involving events:
using System;
public class Program
{
public static void Main(string[] args)
{
Action action = () => Console.WriteLine("Foo");
// This is a stand-in for the event.
Action x = null;
x += action;
x += action;
x += action;
x -= action;
x(); // Prints Foo twice
}
}
Strictly speaking, an event subscription could do anything. You could implement an event like this:
private EventHandler weirdEvent;
public event EventHandler WeirdEvent
{
add { weirdEvent += value; } // Subscribe as normal
remove { weirdEvent = null; } // I'm bored with *all* the handlers
}
But normally events just delegate to Delegate.Combine and Delegate.Remove, which are the methods that += and -= are syntactic sugar for in C#.
My article on events and delegates contains more details about exactly what happens with combination and removal.
private void button1_Click(object sender, EventArgs e)
{
// set breakpoint
}
this.button1.Click += new System.EventHandler(this.button1_Click);
this.button1.Click += new System.EventHandler(this.button1_Click);
this.button1.Click += new System.EventHandler(this.button1_Click);
this.button1.Click -= new System.EventHandler(this.button1_Click);
Invoking the click event will show the breakpoint hit twice.
This should be also safe.
Property_Saved += Property_Saved_Handler;
Property_Saved -= Property_Saved_Handler;
Property_Saved -= Property_Saved_Handler;
Just make your own test using GetInvocationList
public delegate void MyEventHandler(string s);
public event MyEventHandler MyEvent;
MyEventHandler #event = s => { };
MyEvent += #event;
Console.WriteLine(MyEvent.GetInvocationList().Length);
MyEvent += #event;
Console.WriteLine(MyEvent.GetInvocationList().Length);
MyEvent -= #event;
Console.WriteLine(MyEvent.GetInvocationList().Length);
This will print
1
2
1
Related
This question already has an answer here:
How to remove a lambda event handler [duplicate]
(1 answer)
Closed 1 year ago.
Why C# allows to unsubscribe from the event when the event handler is defined as a function, but not when the event handler is defined as a delegate?
Consider the following code that works:
void SomeFunction()
{
var eventRaiser = ClassRaisingEvent.GetEventRaiser();
void handler(object sender, EventArgs ev)
{
ProcessData(ev);
eventRaiser.OnEvent -= handler;
}
eventRaiser.OnEvent += handler
eventRaiser.Process();
}
But this fails to compile at the indicated spot:
void SomeFunction()
{
var eventRaiser = ClassRaisingEvent.GetEventRaiser();
DelegateType handler = (object sender, EventArgs ev) =>
{
ProcessData(ev);
eventRaiser.OnEvent -= handler; // FAILS here with "Use of unassigned local variable 'handler '"
}
eventRaiser.OnEvent += handler
eventRaiser.Process();
}
EDIT: This question is not how to unsubscribe. This is why (in technical sense) function name is captured in the scope of the function, but the delegate's isn't.
EDIT2: Answer here (and esp. pt. 2 in the answer) explains the behavior I'm seeing.
The local function is defined at compile time, just like other functions. The handler variable, however, is only known at runtime. So, as the compiler tells you, you cannot use the handler variable before it's been assigned a value, which means after the closing } of your delegate.
What you could do to get your second snippet working, is to initialize the variable first:
void SomeFunction()
{
var eventRaiser = ClassRaisingEvent.GetEventRaiser();
DelegateType handler = null;
handler = (object sender, EventArgs ev) =>
{
ProcessData(ev);
eventRaiser.OnEvent -= handler;
};
eventRaiser.OnEvent += handler;
eventRaiser.Process();
}
I have a class that implements PropertyChanged. I do something similar to this to subscribe to it:
p.PropertyChanged += (s, a) => {
switch ( a.PropertyName) {
...
}
}
How can I later unsubscribe the above code from the p.PropertyChanged ?
Equivalent of (which clearly won't work):
p.PropertyChanged -= (s, a) => {
switch ( a.PropertyName) {
...
}
}
You must put it in a variable:
PropertyChangedEventHandler eventHandler = (s, a) => {
...
};
// ...
// subscribe
p.PropertyChanged += eventHandler;
// unsubscribe
p.PropertyChanged -= eventHandler;
From the docs:
It is important to notice that you cannot easily unsubscribe from an event if you used an anonymous function to subscribe to it. To unsubscribe in this scenario, it is necessary to go back to the code where you subscribe to the event, store the anonymous method in a delegate variable, and then add the delegate to the event. In general, we recommend that you do not use anonymous functions to subscribe to events if you will have to unsubscribe from the event at some later point in your code.
As an addition to #Sweeper's answer, you can accomplish the same using event handler method, without the burden of lambda expressions:
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
...
}
}
Which you can then use to subscribe to the PropertyChanged event:
p.PropertyChanged += OnPropertyChanged;
And to unsubscribe:
p.PropertyChanged -= OnPropertyChanged;
Additional info from the docs:
To respond to an event, you define an event handler method in the event receiver. This method must match the signature of the delegate for the event you are handling. In the event handler, you perform the actions that are required when the event is raised, such as collecting user input after the user clicks a button. To receive notifications when the event occurs, your event handler method must subscribe to the event.
How to use the this (or something of that kind) referring to the delegate instance instead of the class instance ?
instance.OnEventFoo += delegate()
{
if (condition)
{
instance.OnEventBar += this;
}
};
Since you can't refer to a variable before it is declared, you have to:
first declare the variable,
then assign a delegate,
then register the handler with the event.
// Add an anonymous delegate to the events list and auto-removes automatically if item disposed
DataRowChangeEventHandler handler = null;
handler = (sender, args) =>
{
if (condition)
{
// need to remove this delegate instance of the events list
RowChanged -= handler;
}
};
something.RowChanged += handler;
You need to store it in a variable somewhere. For example:
EventHandler rowChanged = null; // to avoid "uninitialized variable" error
rowChanged = (s, e) =>
{
if (condition)
{
// this will unsubscribe from the event as expected
RowChanged -= rowChanged;
}
};
I am adding an event handler like this:
theImage.MouseMove += new MouseEventHandler(theImage_MouseMove);
but in my application, this code gets run every time the page is shown, so I want to attach the event handler only once, but how can I tell if a handler has been set yet or not, something like this:
if(theImage.MouseMove == null) //error
theImage.MouseMove += new MouseEventHandler(theImage_MouseMove);
I might me missing something, but if you just want to make sure that the handle is only called once, why don't you use -= before adding it. Something like this:
public static void Main(string[] args)
{
var timer = new Timer(1000);
timer.Elapsed -= new ElapsedEventHandler(timer_Elapsed);
timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
timer.Elapsed -= new ElapsedEventHandler(timer_Elapsed);
timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
timer.Start();
System.Threading.Thread.Sleep(4000);
}
static void timer_Elapsed(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Hit!");
}
The handler will only run once every second.
You can shorten it down if you only want to attach once:
theImage.MouseMove -= theImage_MouseMove; //If it wasn't attached, doesn't matter
theImage.MouseMove += theImage_MouseMove;
I'm not sure if this is the best solution, but the way I usually do this is to simply use an unsubscribe before the subscribe.
If you do something like:
TheImage.MouseMove -= new MouseEventHandler(theImage_MouseMove);
TheImage.MouseMove += new MouseEventHandler(theImage_MouseMove);
It will only ever get added once. If it doesn't already exist (the first time it's triggered), the -= doesn't hurt anything if it hasn't been subscribed to previously.
theImage.MouseMove.GetInvocationList().Length
I have the following code:
public List<IWFResourceInstance> FindStepsByType(IWFResource res)
{
List<IWFResourceInstance> retval = new List<IWFResourceInstance>();
this.FoundStep += delegate(object sender, WalkerStepEventArgs e)
{
if (e.Step.ResourceType == res) retval.Add(e.Step);
};
this.Start();
return retval;
}
Notice how I register my event member (FoundStep) to local in-place anonymous function.
My question is: when the function 'FindStepByType' will end - will the anonymous function be removed automatically from the delegate list of the event or I have to manually remove it before steping out the function? (and how do I do that?)
I hope my question was clear.
Your code has a few problems (some you and others have identified):
The anonymous delegate cannot be removed from the event as coded.
The anonymous delegate will live longer than the life of the method calling it because you've added it to FoundStep which is a member of this.
Every entry into FindStepsByType adds another anonymous delegate to FoundStep.
The anonymous delegate is a closure and effectively extends the lifetime of retval, so even if you stop referencing retval elsewhere in your code, it's still held by the anonymous delegate.
To fix this, and still use an anonymous delegate, assign it to a local variable, and then remove the handler inside a finally block (necessary in case the handler throws an exception):
public List<IWFResourceInstance> FindStepsByType(IWFResource res)
{
List<IWFResourceInstance> retval = new List<IWFResourceInstance>();
EventHandler<WalkerStepEventArgs> handler = (sender, e) =>
{
if (e.Step.ResourceType == res) retval.Add(e.Step);
};
this.FoundStep += handler;
try
{
this.Start();
}
finally
{
this.FoundStep -= handler;
}
return retval;
}
With C# 7.0+ you can replace the anonymous delegate with a local function, achieving the same effect:
public List<IWFResourceInstance> FindStepsByType(IWFResource res)
{
var retval = new List<IWFResourceInstance>();
void Handler(object sender, WalkerStepEventArgs e)
{
if (e.Step.ResourceType == res) retval.Add(e.Step);
}
FoundStep += Handler;
try
{
this.Start();
}
finally
{
FoundStep -= Handler;
}
return retval;
}
Below is approach about how unsubscribe event in anonymous method:
DispatcherTimer _timer = new DispatcherTimer();
_timer.Interval = TimeSpan.FromMilliseconds(1000);
EventHandler handler = null;
int i = 0;
_timer.Tick += handler = new EventHandler(delegate(object s, EventArgs ev)
{
i++;
if(i==10)
_timer.Tick -= handler;
});
_timer.Start();
No, it will not be removed automatically. In this sense, there's not a difference between an anonymous method and a "normal" method. If you want, you should manually unsubscribe from the event.
Actually, it'll capture other variables (e.g. res in your example) and keep them alive (prevents garbage collector from collecting them) too.
When using an anonymous delegate (or a lambda expression) to subscribe to an event does not allow you to easily unsubscribe from that event later. An event handler is never automatically unsubscribed.
If you look at your code, even though you declare and subscribe to the event in a function, the event you are subscribing to is on the class, so once subscribed it will always be subscribed even after the function exits. The other important thing to realize is that each time this function is called, it will subscribe to the event again. This is perfectly legal since events are essentially multicast delegates and allow multiple subscribers. (This may or may not be what you intend.)
In order to unsubscribe from the delegate before you exit the function, you would need to store the anonymous delegate in a delegate variable and add the delegate to the event. You should then be able to remove the delegate from the event before the function exits.
For these reasons, if you will have to unsubscribe from the event at some later point it is not recommended to use anonymous delegates. See How to: Subscribe to and Unsubscribe from Events (C# Programming Guide) (specifically the section titled "To subscribe to events by using an anonymous method").