Click here to Skip to main content
15,867,686 members
Articles / Programming Languages / C#
Article

Facts and Fallacies of Events in C#

Rate me:
Please Sign up or sign in to vote.
4.76/5 (45 votes)
24 Jul 2007CPOL6 min read 92.1K   652   64   20
Delegates: how to add them to an event, how they get removed and when that is necessary

Introduction

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.

Delegates and events

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:

C#
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) 
    {
        ...
    }
}

A popular mistake: adding the same delegate more than once

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!

A little magic

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:

C#
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:

C#
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 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:

C#
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:

C#
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:

  • Every 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, 0
  • The first "remove delegate" did not remove all delegates; it removed just one
  • Removing more delegates than we added in the first place did not throw an exception; it had no effect at all

C# 2.0

Originally, C# required an explicit delegate to be added to or removed from an event, as in:

C#
// 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:

C#
// 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.

The final question: do we have to remove event handlers?

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:

  • Most of the time, all delegates are used internally and the situation is simple. In our earlier example, the button is part of a class and the click handler method also belongs to that class. So in a sense it is an "internal delegate" and an "internal event," something that is invisible from the outside. When an instance of that class no longer is reachable by the 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.
  • As soon as one delegate is used "outside" of its class, the situation is different. If an instance of 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.

Other points of interest

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:

  • Create a simple log() method to get timestamps on major actions
  • Add Console.ReadLine() to keep the command window from closingng
  • Use #define, #if, #else, #endif statements for conditional compilation

History

  • 24 July, 2007 -- LP#EventHandlerRemoval 1.0, first release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Belgium Belgium
I am an engineer with a background in electronics, software and mathematics.

I develop technical software, both for embedded systems and for desktop equipment. This includes operating systems, communication software, local networks, image processing, machine control, automation, etc.

I have been using all kinds of microcontrollers and microprocessors (Intel 4004/8080/8051/80386/Pentium, Motorola 680x/680x0/ColdFire/PowerPC, Microchip PIC, Altera NIOS, and many more), lots of programming languages (all relevant assemblers, Fortran, Basic, C, Java, C#, and many more), and different operating systems (both proprietary and commercial).

For desktop applications and general development tools I have been using both UNIX systems and Mac/MacOS for many years, but I have switched to x86-based PCs with Windows, Visual Studio and the .NET Framework several years ago.

I specialize in:
- cross-platform development (making software that runs on diverse hardware/OS combinations)
- instruction set simulation
- improving software performance, i.e. making sure the software runs the job at hand in as short a time as possible on the given hardware. This entails algorithm selection, implementation design, accurate measurements, code optimisation, and sometimes implementing virtual machines, applying SIMD technology (such as MMX/SSE), and more.

Comments and Discussions

 
Generalinteresting read [modified] Pin
Luc Pattyn6-Oct-08 14:38
sitebuilderLuc Pattyn6-Oct-08 14:38 
GeneralUnsubscribe to an object when in a 'using' block Pin
Gareth H10-Jul-08 7:27
Gareth H10-Jul-08 7:27 
AnswerRe: Unsubscribe to an object when in a 'using' block Pin
Luc Pattyn10-Jul-08 7:41
sitebuilderLuc Pattyn10-Jul-08 7:41 
GeneralRe: Unsubscribe to an object when in a 'using' block Pin
Gareth H10-Jul-08 7:57
Gareth H10-Jul-08 7:57 
GeneralRe: Unsubscribe to an object when in a 'using' block Pin
Luc Pattyn10-Jul-08 8:00
sitebuilderLuc Pattyn10-Jul-08 8:00 
GeneralYou've got my five :) Pin
Adam Tibi31-Jul-07 5:14
professionalAdam Tibi31-Jul-07 5:14 
AnswerRe: You've got my five :) Pin
Luc Pattyn31-Jul-07 5:26
sitebuilderLuc Pattyn31-Jul-07 5:26 
GeneralGood work! Pin
Martin#30-Jul-07 20:33
Martin#30-Jul-07 20:33 
GeneralRe: Good work! Pin
Luc Pattyn31-Jul-07 0:54
sitebuilderLuc Pattyn31-Jul-07 0:54 
GeneralCommon misconceptions regarding events and delegates in C# Pin
Dr.Drew30-Jul-07 12:09
Dr.Drew30-Jul-07 12:09 
Here are some suggestions for your article.


In your article, you state, "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." To me, this implies that an event is different from a delegate in that the event is a collection of delegates. In C#, both events and delegates (specifically instances of types declared with the "delegate" keyword) are collections, or more specifically, linked lists. So the difference between an event and a delegate is that an event restricts the adding and removing of a method to the += and -= operators. Furthermore, an event can only be invoked from the class where it is declared. Other than minor differences like the ones I mentioned, events and delegates are practically the same (both act like collections). In C#, declaring a type using the "delegate" keyword actually generates a class that derives from MulticastDelegate.



Microsoft furthers common misconceptions about a delegate by stating, "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."



This is technically incorrect, since a delegate declaration is in essence a MulticastDelegate capable of encapsulating one or more static or instance methods.



Delegates (types declared using the keyword "delegate") are different than C++ function pointers in that delegates are much more than simple function pointers. MulticastDelegates are classes with linked list structures, and a relatively significant amount of code for managing linked list structures.



Regards,
Fernando
AnswerRe: Common misconceptions regarding events and delegates in C# Pin
Luc Pattyn3-Aug-07 14:51
sitebuilderLuc Pattyn3-Aug-07 14:51 
GeneralQuestion on remove delegates Pin
krn_2k25-Jul-07 0:14
krn_2k25-Jul-07 0:14 
GeneralRe: Question on remove delegates Pin
Urs Enzler25-Jul-07 0:42
Urs Enzler25-Jul-07 0:42 
AnswerRe: Question on remove delegates Pin
Luc Pattyn25-Jul-07 1:05
sitebuilderLuc Pattyn25-Jul-07 1:05 
GeneralNice article. Pin
Marc Leger24-Jul-07 6:55
Marc Leger24-Jul-07 6:55 
AnswerRe: Nice article Pin
Luc Pattyn24-Jul-07 7:11
sitebuilderLuc Pattyn24-Jul-07 7:11 
Generaltest (unrelated to article) Pin
Luc Pattyn24-Jul-07 15:41
sitebuilderLuc Pattyn24-Jul-07 15:41 
JokeRe: test (unrelated to article) Pin
Paul Conrad29-Jul-07 6:59
professionalPaul Conrad29-Jul-07 6:59 
AnswerRe: test (unrelated to article) Pin
Luc Pattyn29-Jul-07 7:10
sitebuilderLuc Pattyn29-Jul-07 7:10 
GeneralRe: Nice article. Pin
roman.wagner30-Jul-07 21:11
roman.wagner30-Jul-07 21:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.