Events: Add and Remove Functions

Chapter 23 showed how to use delegates to pass a reference to a method so it can be called in a general way. Being able to call a method in such a way is useful in graphical user interfaces, such as the one provided by the classes in System.Windows.Forms. It’s fairly easy to build such a framework by using delegates, as shown in this example:

using System;

public class Button {

public delegate void ClickHandler(object sender, EventArgs e);

public ClickHandler Click;

protected void OnClick()

{

if (Click != null)

Click(this, null);

}

public void SimulateClick()

{

OnClick();

}

}

class Test {

static public void ButtonHandler(object sender, EventArgs e)

{

Console.WriteLine(“Button clicked”);

}

public static void Main()

{

Button button = new Button();

button.Click = new Button.ClickHandler(ButtonHandler);

button.SimulateClick();

}

}

The Button class is supporting a click “event”1 by having the ClickHandler delegate tell what kind of method can be called to hook up, and a delegate instance can then be assigned to the event. The OnClick() method then calls this delegate, and everything works fine—at least in this simple case.

The situation gets more complicated in a real-world scenario. In real applications, a button such as this one lives in a form, and clicking the button might be of interest to more than one area of the application. Doing this isn’t a problem with delegates because more than one method can be called from a single delegate instance. In the previous example, if another class also wanted to be called when the button was clicked, the += operator can be used, like this:

button.Click += new Button.ClickHandler(OtherMethodToCall);

Unfortunately, if the other class wasn’t careful, it might do the following:

button.Click = new Button.ClickHandler(OtherMethodToCall);

This would be bad, as it’d mean that your ButtonHandler would be unhooked and only the new method would be called.

Similarly, to unhook from the click, the right thing to do is use this code:

button.Click -= new Button.ClickHandler(OtherMethodToCall);

but the following might be used instead:

button.Click = null;

This is also wrong.

What you need is some way of protecting the delegate field so it’s accessed only using += and -=.

1. Add and Remove Functions

An easy way to do this is to make the delegate field private and write a couple of methods that can be used to add or remove delegates:

using System;

public class Button {

public delegate void ClickHandler(object sender, EventArgs e);

private ClickHandler click;

public void AddClick(ClickHandler clickHandler)

{

click += clickHandler;

}

public void RemoveClick(ClickHandler clickHandler)

{

click -= clickHandler;

}

protected void OnClick()

{

if (click != null) click(this, null);

}

public void SimulateClick()

{

OnClick();

}

}

class Test {

static public void ButtonHandler(object sender, EventArgs e)

{

Console.WriteLine(“Button clicked”);

}

public static void Main()

{

Button button = new Button();

button.AddClick(new Button.ClickHandler(ButtonHandler)); button.SimulateClick();

button.RemoveClick(new Button.ClickHandler(ButtonHandler));

}

}

In this example, the AddClick() and RemoveClick() methods have been added, and the delegate field is now private. It’s now impossible for users of the class to do the wrong thing when they hook or unhook.

This example is reminiscent of the example in Chapter 19. It had two accessor methods, and adding properties made those two methods look like a field. Let’s add a feature to the compiler so there’s a “virtual” delegate named Click. It can write the AddClick() and RemoveClick() methods for you, and it can also change a use of += or -= to the appropriate add or remove call. This gives you the advantage of having the Add and Remove methods without having to write them.

You need a keyword for this compiler enhancement, and event seems like a good choice: using System;

public class Button {

public delegate void ClickHandler(object sender, EventArgs e);

public event ClickHandler Click;

protected void OnClick()

{

if (Click != null)

Click(this, null);

}

public void SimulateClick()

{

OnClick();

}

}

class Test {

static public void ButtonHandler(object sender, EventArgs e)

{

Console.WriteLine(“Button clicked”);

}

public static void Main()

{

Button button = new Button();

button.Click += new Button.ClickHandler(ButtonHandler);

button.SimulateClick();

button.Click -= new Button.ClickHandler(ButtonHandler);

}

}

When the event keyword is added to a delegate, the compiler creates a private field and then writes public add_Click() and remove_Click() methods. It also emits a bit of metadata that says there’s an event named Click and that event is associated with add and remove methods with these names so that object browsers and such can tell there’s an event on this class.

In Main(), the event is accessed like a delegate; but since the add and remove methods are the only ways to access the private delegate, += and -= are the only operations that can be performed on the event.

That’s the basic story for events. The arguments to the event handler, obj ect sender and EventArgs e, are by convention and should be followed by other classes that expose events. The sender argument allows the user of the code to know which object fired the event, and the e argument contains the information associated with the event. In this case, there’s no additional information to pass, so EventArgs is used. If additional information needed to be passed, a class should be derived from EventArgs with the additional information. For example, the KeyEventArgs class in the framework looks like this:

using System;

using System.Windows.Forms;

class KeyEventArgs: EventArgs {

Keys keyData;

KeyEventArgs(Keys keyData)

{

this.keyData = keyData;

}

public Keys KeyData {

get

{

return(keyData);

}

}

// other functions here…

}

The OnKey method will take a parameter of type Keys, encapsulate it into a KeyEventArgs class, and then call the delegate.

Source: Gunnerson Eric, Wienholt Nick (2005), A Programmer’s Introduction to C# 2.0, Apress; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *