![]() |
Languages »
C# »
Delegates and Events
Intermediate
License: The Code Project Open License (CPOL)
Facts and Fallacies of Events in C#By Luc PattynDelegates: how to add them to an event, how they get removed and when that is necessary |
C#, Windows, .NET 2.0, Visual Studio, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
If you don't know what delegates and events are, don't read on. Go read an introductory book on C# first. If you do know what events and delegates are and you have used them successfully, but still have some questions about them, this article may shed some light.
MSDN holds some pretty accurate definitions for delegates and events, as it should:
A delegate declaration defines a reference type that can be used to encapsulate a method with a specific signature. A delegate instance encapsulates a static or an instance method. Delegates are roughly similar to function pointers in C++. However, delegates are type-safe and secure.
Events are used on classes and structs to notify objects of occurrences that may affect their state. To add an event to a class requires using the event keyword and providing a delegate type and a name for the event.
To recap that in layman's terms: a delegate contains the reference to an object and a method that can operate on that object -- say "a function pointer" and a "this" -- whereas an event resembles a collection capable of holding such delegates and firing them one after another. As an example, one can install an "event handler" to be called on a mouse click using the following lines of code:
public class myForm : Form
{
private Button button=new Button();
...
// constructor
public myForm()
{
...
button.Click+=new EventHandler(button_Click);
...
}
// event handler, fires when button gets clicked
private void button_Click(object sender, EventArgs e)
{
...
}
}
Normally, you want a delegate to be called just once: one btn_click() for every click of the button. When you add the same delegate more than once to an event, the handler will be called more than once. Most of the time, that was not the intention and the extra adds are a mistake. That mistake may constitute a bug: hitting the "A" key should not enter two characters "A" in a textbox. Simple mistakes like that will be noticed immediately.
Sometimes, executing the handler more than once does not result in an error; it just wastes CPU cycles. For instance, executing a Focus handler twice probably will not result in a logical error. Of course, if for some reason you keep adding the same Paint handler over and over to the Paint event, in the end the responsiveness of the application will be completely ruined. It may take you a while to figure out where the mistake is!
The event works somewhat like a collection: you can add delegates to it and, later on when you are no longer interested, you can remove them again. Some people scrupulously store the delegate they are about to add to the event so that they can later use the reference for removing it again, as in:
private EventHandler buttonClickHandler =
new EventHandler(button_Click);
...
button.Click += buttonClickHandler;
...
button.Click -= buttonClickHandler;
Other people, and most books on C#, use a different approach: they create a delegate and add it. Later on, they create another delegate and remove it, as in:
button.Click += new EventHandler(button_Click);
...
button.Click -= new EventHandler(button_Click);
So basically now the events "collection" seems able to remove an object that is different from, but equivalent to, another object that was added earlier. What this actually means is that the run-time system, when executing the -= line, parses the delegate and looks for one with the same object and method pointer, i.e. this and button_click respectively. If it finds one or more, it removes one. If not, nothing happens and no exception gets thrown. All of this gets confirmed by the test program.
The program is a very simple console application. It creates one button that does not get a parent and hence remains invisible. The program then plays around with a delegate to handle the click events. It simulates some clicks using the Button.PerformClick() method, which fortunately also works for invisible buttons! Most importantly, the program logs all that is going on, so the log can confirm the theory. This is the heart of the test sequence:
public void Run()
{
doClick();
addDelegate();
doClick();
addDelegate();
doClick();
removeDelegate();
doClick();
removeDelegate();
doClick();
removeDelegate();
doClick();
log("Done");
}
private static void log(string s)
{
if (s.Length!=0) s=DateTime.Now.ToString("ss.fff ")+s;
Console.WriteLine(s);
}
private void doClick()
{
log("CLICK");
btn.PerformClick();
}
private void addDelegate()
{
delegateCount++;
log("ADD DELEGATE #"+delegateCount);
btn.Click+=new EventHandler(btn_Click);
}
private void removeDelegate()
{
log("REMOVE DELEGATE #"+delegateCount);
delegateCount--;
try {btn.Click-=new EventHandler(btn_Click);}
catch { log("failed to remove handler"); }
}
private void btn_Click(object sender, EventArgs e)
{
clicks++;
log(" got click #"+clicks);
}
And this is the log that gets produced:
29.750 CLICK
29.750 ADD DELEGATE #1
29.750 CLICK
29.750 got click #1
29.750 ADD DELEGATE #2
29.750 CLICK
29.750 got click #2
29.750 got click #3
29.750 REMOVE DELEGATE #2
29.750 CLICK
29.750 got click #4
29.750 REMOVE DELEGATE #1
29.750 CLICK
29.750 REMOVE DELEGATE #0
29.750 CLICK
29.750 Done
These are the observations we made:
CLICK is followed by a number of "got click" messages equal to the number of delegates currently in the event: first none and then 1, 2, 1, 0Originally, C# required an explicit delegate to be added to or removed from an event, as in:
// add a click handler, the original way (works since .NET 1.0)
button.Click += new EventHandler(button_Click);
...
// remove a click handler, the original way (works since .NET 1.0)
button.Click -= new EventHandler(button_Click);
The above works for all .NET versions. The new version of C#, C# 2.0, also accepts a shorthand notation, as in:
// add a click handler, the shorthand (works since .NET 2.0)
button.Click += button_Click;
...
// remove a click handler, the shorthand (works since .NET 2.0)
button.Click -= button_Click;
This generates exactly the same MSIL code. The compiler deduces which delegate needs to be instantiated from the declaration of the Click event itself. So, the magic described earlier is still going on. A new delegate is being created in order to remove an earlier one, but this is no longer apparent from the source code! It now seems like the button_click first got added and then removed again.
Of course we will remove a delegate if the object remains active, but does not want to receive the notifications anymore. However, what if the object is basically done; it has no use anymore and we hope it will soon be garbage collected? We realize that a delegate contains a reference to the object to which the method applies, so will the existence of delegates prevent it from being collected? There are two different situations:
Main() method and its descendants, then the object is a candidate for garbage collection. The event inside the object should not and will not prevent that.class1 attaches its delegate to an event of another class, i.e. class2, then that counts as a reference from class2 to class1. Hence, as long as the class2 object is alive, it will keep the instance of class1 alive. To end this dependency, the class1 instance should remove its delegate. Conclusion: you should remove delegates from events when they reach outside the class itself; i.e. when you subscribe to external events, you should end your subscription when you are done. Failing to do so will keep your object alive longer than necessary.
The following items relate to recent topics on one of the CodeProject message boards. Note that some of them are not discussed in this article, but are present in the source code:
log() method to get timestamps on major actions Console.ReadLine() to keep the command window from closingng#define, #if, #else, #endif statements for conditional compilation
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 24 Jul 2007 Editor: Genevieve Sovereign |
Copyright 2007 by Luc Pattyn Everything else Copyright © CodeProject, 1999-2009 Web18 | Advertise on the Code Project |