Chapter 9. Delegating Those Important Events

In This Chapter

  • Using delegates to solve the callback problem

  • Using delegates to customize a method

  • Implementing delegates by using anonymous methods

  • Using C# events to notify the world when interesting events happen

This chapter looks into a corner of C# that has been around since the birth of the language, but one that I've avoided because it's challenging stuff. However, if you can bear with me — and I try to go as easy on you as possible — the payoff is well worth it.

E.T., Phone Home — The Callback Problem

If you've seen the Steven Spielberg movie E.T., the Extraterrestrial (1982), you watched the cute but ugly little alien stranded on Earth try to build an apparatus from old toy parts with which he could "phone home." He needed his ship to pick him up.

It's a big jump from E.T. to C#, but code sometimes needs to phone home, too. For example, you may have wondered how the Windows progress bar works. It's the horizontal "bar" that gradually fills up with coloring to show progress during a lengthy operation, such as copying files. (On my machine, of course, good old Murphy's law — "Whatever can go wrong will go wrong" — seems to fill it up well before the task is finished. Figure 9-1 shows a green progress bar (though it isn't easy being green in a black-and-white book).

The progress bar is based on a lengthy operation's periodic pause to "phone home." In programmerese, it's a callback. Usually, the lengthy operation estimates how long its task should take and then checks frequently to see how far it has progressed. Periodically, the progress bar sends a signal by calling a callback method back on the mother ship — the class that kicked off the long operation. The mother ship can then update its progress bar.

The trick is that you have to supply this callback method for the long operation to use.

That callback method may be on the same class as the lengthy operation — such as phoning your sister on the other side of the house. Or, more often, it's on another class that knows about the progress bar — such as phoning Aunt Maxie in Minnesota. Somehow, at its start, the lengthy operation has been handed a mechanism for phoning home — sort of like giving your kid a cellphone so that she can call you at 10 p.m.

This chapter talks about how your code can set up this callback mechanism and then invoke it to phone home when needed.

Note

Callbacks are used a lot in Windows programming, typically for a piece of code, down in your program's guts, to notify a higher-level module that the task has finished, to ask for needed data, or to let that module take a useful action, such as write a log entry or update a progress bar.

Making progress with the Windows ProgressBar control.

Figure 9-1. Making progress with the Windows ProgressBar control.

Defining a Delegate

C# provides delegates for making callbacks — and a number of other tasks. Delegates are the C# way (the .NET way, really, because any .NET language can use them) for you to pass around methods as though they were data.

You're saying, "Here, execute this method when you need it" (and then handing over the method to execute).

This chapter helps you get a handle on that concept, see its usefulness, and start using it yourself.

You may be an experienced coder who will recognize immediately that delegates are similar to C/C++ function pointers — only much, much better. But I'm assuming in this section that you aren't and you don't.

Think of a delegate as a vehicle for passing a callback method to a "workhorse" method that needs to call you back or needs help with that action, as in doing the same action to each element of a collection. Because the collection doesn't know about your custom action, you need a way to provide the action for the collection to carry out. Figure 9-2 shows how the parts of this scheme fit together.

Sending your delegate to the bungee-jump on your behalf.

Figure 9-2. Sending your delegate to the bungee-jump on your behalf.

A delegate is a data type, similar to a class. As with a class, you create an instance of the delegate type in order to use the delegate. Figure 9-2 shows the sequence of events in the delegate's life cycle as you complete these steps:

  1. Define the delegate type (in much the same way as you would define a class).

    Sometimes, C# has already defined a delegate you can use. Much of the time, though, you need to define your own, custom delegates.

    Note

    Under the surface, a delegate is a class, derived from the class System.MulticastDelegate, which knows how to store one or more "pointers" to methods and invoke them for you. Relax: The compiler writes the class part of it for you.

  2. Create an instance of the delegate type — such as instantiating a class.

    Note

    During creation, you hand the new delegate instance the name of a method that you want it to use as a callback or an action method.

  3. Pass the delegate instance to a workhorse method, which has a parameter of the delegate type. That's the doorway through which you insert the delegate instance into the workhorse method.

    It's like smuggling a candy bar into a movie theater — except that in this example, the movie theater expects, even invites, the contraband candy.

  4. When the workhorse method is ready — for example, when it's time to update the progress bar — the workhorse "invokes" the delegate, passing it any expected arguments.

  5. Invoking the delegate in turn invokes (calls) the callback method that the delegate "points" to. Using the delegate, the workhorse phones home. (Older readers may remember Mr. Ed: "A horse is a horse. . . .")

This fundamental mechanism solves the callback problem — and it has other uses too.

Note

Delegate types can also be generic, allowing you to use the same delegate for different data types, much as you can instantiate a List<T> collection for string or int. Book I covers this in detail.

Pass Me the Code, Please — Examples

Let's jump right into a couple of examples — and solve the callback problem discussed at the beginning of this chapter.

I delegated the example to Igor

In this section, I walk you through two examples of using a callback — a delegate instance phoning home to the object that created it, like E.T. But first take a look at some common variations on what you can use a callback delegate for:

  • To notify the delegate's home base of an event: A lengthy operation has finished or made some progress or perhaps run into an error. "Mother, this is E.T. Can you come get me at Elliot's house?"

  • To call back to home base to ask for the necessary data to complete a task: "Honey, I'm at the store. Should I get white bread or wheat?"

  • Note

    More generally, to customize a method: The method you're customizing provides a framework, and its caller supplies a delegate to do the work. "Honey, take this grocery list to the store and follow it exactly." The delegate method carries out a task that the customized method needs done (but can't handle by itself). The customized method is responsible for invoking the delegate at the appropriate moment.

First, a simple example

The SimpleDelegateExample program on the Web site demonstrates a simple delegate:

Note

// SimpleDelegateExample -- Demonstrate a simple delegate callback.
using System;
namespace SimpleDelegateExample
{
  class Program
  {
    delegate int MyDelType(string name);  // Inside class or inside namespace

    static void Main(string[] args)
    {
      // Create a delegate instance pointing to the CallBackMethod below.
      // Note that the callback method is static, so you prefix the name
      // with the class name, Program.
      MyDelType del = new MyDelType(Program.CallBackMethod);
      // Call a method that will invoke the delegate.
      UseTheDel(del, "hello");
      // Wait for user to acknowledge results.
      Console.WriteLine("Press Enter to terminate...");
      Console.Read();
    }
    // UseTheDel -- A "workhorse" method that takes a MyDelType delegate
    //    argument and invokes the delegate. arg is a string I want to pass
    //    to the delegate invocation.
    private static void UseTheDel(MyDelType del, string arg)
    {
      if (del == null) return; // Don't invoke a null delegate!
      // Here's where you invoke the delegate.
      // What's written here? A number representing the length of arg.
     Console.WriteLine("UseTheDel writes {0}", del(arg));
    }
    // CallBackMethod -- A method that conforms to the MyDelType
    //    delegate signature (takes a string, returns an int).
    //    The delegate will call this method.
    public static int CallBackMethod(string stringPassed)
    {
      // Leave tracks to show you were here.
      // What's written here? stringPassed.
      Console.WriteLine("CallBackMethod writes: {0}", stringPassed);
      // Return an int.
      return stringPassed.Length;  // Delegate requires an int return.
    }
  }
}

The delegate-related parts of this example are highlighted in boldface.

First you see the delegate definition. MyDelType defines a signature — you can pass any method with the delegate; such a method must take a string argument and return an int. Second, the CallBackMethod(), defined at the bottom of the listing, matches that signature. Third, Main() creates an instance of the delegate, called del, and then passes the delegate instance to a "workhorse" method, UseTheDel(), along with some string data, "hello", that the delegate requires.

In that setup, here's the sequence of events:

  1. UseTheDel() takes two arguments, a MyDelType delegate, and a string that it calls arg. So, when Main() calls UseTheDel, it passes my delegate instance to be used inside the method. When I created the delegate instance, del, in Main(), I passed the name of the CallBackMethod() as the method to be called. Because CallBackMethod() is static, I had to prefix the name with the class name, Program. I tell you more about it later in this chapter.

  2. Inside UseTheDel(), the method ensures that the delegate isn't null and then starts a WriteLine() call. Within that call, before it finishes, the method invokes the delegate by calling del(arg). arg is just something you can pass to the delegate, which causes the CallBackMethod() to be called.

  3. Inside CallBackMethod(), the method writes its own message, including the string that was passed when UseTheDel() invoked the delegate. Then CallBackMethod() returns the length of the string it was passed, and that length is written out as the last part of the WriteLine() in UseTheDel().

The output looks like this:

CallBackMethod writes: hello
UseTheDel writes 5
Press Enter to terminate...

UseTheDel() phones home and CallBackMethod() answers the call.

A More Real-World Example

For a more realistic example than SimpleDelegateExample, I show you how to write a little app that puts up a progress bar and updates it every time a lengthy method invokes a delegate.

Getting an overview of the bigger example

The SimpleProgress example on the Web site demonstrates the Windows Forms ProgressBar control that I discuss at the top of this chapter. (By the way, this example of Windows graphical programming is the only one in this book — even if it's simple-minded — so I step through it carefully. I urge you to complete the steps as I provide them.)

The example displays a small dialog-box-style window with two buttons and a progress bar (refer to Figure 9-1). When you load the solution example into Visual Studio and then build it, run it, and click the upper button, marked Click to Start, the progress bar runs for a few seconds. You see it gradually fill up, one-tenth of its length at a time. When it's completely full, you can click the Close button to end the program or click Click to Start again.

Putting the app together

To create the sample app on your own, rather than just load it from the Web site example — and experience a bit of Windows graphical programming — follow these steps, working first in design mode, where you're just laying out the appearance of your app.

First create the project and position the necessary controls on your "window":

  1. Choose File

    Putting the app together

    The first thing you see is the form: a window that you lay out yourself using several controls.

  2. Choose View

    Putting the app together
  3. Position the buttons and the ProgressBar so that they look somewhat like the one shown in Figure 9-1. Note the handy guide lines that help with positioning.

Next, set properties for these controls: Choose View

Putting the app together
  1. For the progress bar — named progressBar1 in the code — make sure that the Minimum property is 0, the Maximum property is 100, the Step property is 10, and the Value property is 0.

  2. For the upper button, change the Text property to "Click to Start" and drag the sizing handles on the button image until it looks right and shows all its text.

  3. For the lower button, change the Text property to "Close" and adjust the button's size to your liking.

Tip

In this simple example, you're putting all code in the form class. (The form is your window; its class — here, named Form1 — is responsible for all things graphical.) Generally, you should put all "business" code — the code that does your calculations, data access, and other important work — in other classes. Reserve the form class for code that's intimately involved with displaying elements on the form and responding to its controls. I break that rule here — but the delegate works no matter where its callback method is.

Now, still in design mode, add a handler method for each button:

  1. On the form, double-click the new Close button.

    This action generates a method in the "code behind the form" (or, simply, "the code-behind") — the code that makes the form work. It looks like this — you add the boldfaced code:

    private void button2_Click(object sender, EventArgs e)
    {
      this.Close();  // 'this' refers to the Form1 class.
    }

    Tip

    To toggle between the form's code and its image, choose View

    Putting the app together
  2. Double-click the new Click to Start button to generate its handler method, which looks like the following in the code-behind:

    private void button1_Click(object sender, EventArgs e)
    {
      UpdateProgressCallback callback = UpdateProgressCallback(this.DoUpdate);
      // Do something that needs periodic progress reports.
      // This passes a delegate instance that knows how to update the bar.
      DoSomethingLengthy(callback);
      // Clear the bar so that it can be used again.
      progressBar1.Value = 0;
     }
  3. Add the following callback method to the form class:

    private void DoUpdate()
    {
      progressBar1.PerformStep(); // Tells progress bar to update itself
    }

I walk you through the remaining code, all of it on the form class, in the next section. Later in the chapter, I show you other variations on the delegate that's passed.

Looking at the code

The remaining bits of code tucked into the Form1 class consist of the parts of the delegate life cycle, covered earlier in this chapter. I show you the class and then show you where the parts are. The boldfaced lines are new code that you add beyond the items you added in the previous section:

Note

using System;
using System.Windows.Forms;
namespace SimpleProgress
{
  public partial class Form1 : Form
  {
    // Declare the delegate. This one is void.
    delegate void UpdateProgressCallback();

    public Form1()
    {
      InitializeComponent();
    }
    // DoSomethingLengthy -- My workhorse method takes a delegate.
    private void DoSomethingLengthy(UpdateProgressCallback updateProgress)
    {
      int duration = 2000;
      int updateInterval = duration / 10;
      for (int i = 0; i < duration; i++)
      {
        Console.WriteLine("Something or other");
        // Update every tenth of the duration.
        if ((i % updateInterval) == 0 && updateProgress != null)
        {
          updateProgress();  // Invoke the delegate.
        }
      }
    }
    // DoUpdate -- The callback method
    private void DoUpdate()
    {
      progressBar1.PerformStep();
    }
    private void button1_Click(object sender, EventArgs e)
    {
      // Instantiate the delegate, telling it what method to call.
      UpdateProgressCallback callback = new UpdateProgressCallback(this.
     DoUpdate);
      // Do something that needs periodic progress reports.
      // This passes a delegate instance that knows how to update the bar.
      DoSomethingLengthy(callback);
        // Clear the bar so that it can be used again.
        progressBar1.Value = 0;
    }
    private void button2_Click(object sender, EventArgs e)
    {
      this.Close();
    }
  }
}

Note

The class declaration is interesting as an aside:

public partial class Form1 : Form

The partial keyword indicates that this line is only part of the full class. The rest can be found in the Form1.Designer.cs file listed in Solution Explorer. (Take a look at it.) Later in this chapter, I revisit that file to show you a couple of things about "events." Partial classes, which were introduced in C# 2.0, let you split a class between two or more files. The compiler generates the Form1.Designer.cs file, so don't modify its code directly. You can modify it indirectly, however, by changing elements on the form. Form1.cs is your part.

Tracking the delegate life cycle

Look at the example through the parts of the delegate life cycle:

  1. You define the UpdateProgressCallback delegate near the top of the class:

    delegate void UpdateProgressCallback();

    Methods that this delegate can "point" to will be void, with no parameters. After the delegate keyword, the rest defines the signature of any method that the delegate can point to: its return type and the number, order, and types of its parameters. Delegates don't have to be void — you can write delegates that return any type and take any arguments.

    Defining a delegate defines a type, just as class Student {...} does. You can declare your delegate public, internal, protected, or even private, as needed.

    Tip

    It's considered good form to append the name Callback to the name of a delegate type that defines a callback method, though C# couldn't care less.

  2. You instantiate the delegate and then pass the instance to the DoSomethingLengthy() method in the button1_Click() method:

    UpdateProgressCallback callback =
      new UpdateProgressCallback(this.DoUpdate); // Instantiate the delegate.
    DoSomethingLengthy(callback); // Pass the delegate instance to a method.

    Note

    This delegate "points" to a method on this class (and this is optional). To point to a method on another class, you need an instance of that class (if the method is an instance method), and you pass the method like this:

    SomeClass sc = new SomeClass();
    UpdateProgressCallback callback =
      new UpdateProgressCallback(sc.DoUpdate);

    But if the method is a static method (located anywhere), pass it like this:

    UpdateProgressCallback callback =
      new UpdateProgressCallback(SomeClass.DoUpdate);

    Note

    What you're passing in the instantiation is just the method's name, no parameters. What you pass to DoSomethingLengthy() is the delegate instance, callback (which points to the method).

  3. Your DoSomethingLengthy() method does some "lengthy processing" and periodically pauses to call back to the form so that it can update its progress bar.

    Invoking the delegate inside DoSomethingLengthy() looks like calling a method, complete with parameters, if any:

    updateProgress();  // Invoke the delegate instance passed in.

    DoSomethingLengthy() looks like this:

    private void DoSomethingLengthy(UpdateProgressCallback updateProgress)
    {
      int duration = 2000;
      int updateInterval = duration / 10; // Every 200 milliseconds
      for (int i = 0; i < duration; i++)
      {
        Console.WriteLine("Something or other");
        // Update the form periodically.
        if ((i % updateInterval) == 0 && updateProgress != null)
        {
          updateProgress();  // Invoke the delegate.
        }
      }
    }

    The "lengthy process" doesn't do much. It sets the duration variable to 2,000 loop iterations — a few seconds at runtime, which is more than enough for this demo. Next, the method computes an "update interval" of 200 iterations by dividing the overall duration into tenths. Then the for loop ticks off those 2,000 iterations. For each one, it checks whether it's time to update the user interface, or UI. Most times through the loop, no update occurs. But whenever the if condition is true, the method invokes the UpdateProgressCallback instance that was passed to its updateProgress parameter. That modulo expression, i % updateInterval, comes out only with a 0 remainder, thus satisfying the if condition, once every 200 iterations.

    Note

    Always check a newly instantiated delegate for null before invoking it.

  4. When DoSomethingLengthy() invokes the delegate, the delegate in turn invokes the method you pointed it at — in this case, the DoUpdate() method on the Form1 class.

  5. When called via the delegate, DoUpdate() carries out the update by calling a method on the ProgressBar class named PerformStep():

    private void DoUpdate()
    {
      progressBar1.PerformStep();
    }

    PerformStep(), in turn, fills another 10 percent increment of the bar with green, the amount dictated by its Step property, set to 10 at the outset. Watch the last step closely — it's just a flicker.

  6. Finally, control returns to DoSomethingLengthy(), which continues looping. When the loop runs its course, DoSomethingLengthy() exits, returning control to the button1_Click() method. That method then clears the ProgressBar by setting its Value property to 0. And the app settles down to wait for another click on one of its buttons (or its Close box).

Note

And there you have it. Using the delegate to implement a callback, the program keeps its progress bar up to date. See the list of uses for delegates in the section "I delegated the example to Igor." For more delegate examples, see the DelegateExamples program on the Web site.

Write a custom delegate when you need to define a type for delegate-type parameters so that you can implement a callback. Use predefined delegates for events and the collection classes' Find() and ForEach() methods.

Shh! Keep It Quiet — Anonymous Methods

After you have the gist of using delegates, take a quick look at Microsoft's first cut at simplifying delegates in C# 2.0 a couple of years ago.

To cut out some of the delegate rigamarole, you can use an anonymous method. Anonymous methods are just written in more traditional notation. Although the syntax and a few details are different, the effect is essentially the same whether you use a "raw" delegate, an anonymous method, or a lambda expression. You can find out more about lambda expressions on this book's companions Web site.

An anonymous method creates the delegate instance and the method it "points" to at the same time — right in place, on the fly, tout de suite. Here are the guts of the DoSomethingLengthy() method again, this time rewritten to use an anonymous method (boldfaced):

private void DoSomethingLengthy()  // No arguments needed this time.
{
  ...
  for (int i = 0; i < duration; i++)
  {
    if ((i % updateInterval) == 0)
    {
      UpdateProgressCallback anon = delegate() // Create delegate instance.
      {
        progressBar1.PerformStep();            // Method 'pointed' to
      };
      if(anon != null) anon();                 // Invoke the delegate.
    }
  }
}

The code looks like the delegate instantiations I describe, except that after the = sign, you see the delegate keyword, any parameters to the anonymous method in parentheses (or empty parentheses if none), and the method body. The code that used to be in a separate DoUpdate() method — the method that the delegate "points" to — has moved inside the anonymous method — no more pointing. And this method is utterly nameless.

You still need the UpdateProgressCallback delegate type definition, and you're still invoking a delegate instance, named anon in this example.

Note

Needless to say, this description doesn't cover everything there is to know about anonymous methods, but it's a start. Look up the term anonymous method in Help to see more anonymous method examples in the DelegateExamples program on the Web site. My parting advice is to keep your anonymous methods short.

Stuff Happens — C# Events

One more application of delegates deserves discussion in this section: the C# event, which is implemented with delegates. An event is a variation on a callback but provides a simpler mechanism for alerting "interested observers" whenever an important event occurs. An event is especially useful when more than one anxious relative is waiting for a callback. Events are widely used in C#, especially for connecting the objects in the user interface to the code that makes them work. The buttons in the SimpleProgress example, presented earlier in this chapter, illustrate this use.

The Observer design pattern

It's extremely common in programming for various objects in the running program — those anxious relatives I mentioned — to be "interested in" events that occur on other objects. For example, when the user clicks a button, the form that contains the button "wants" to know about it. Events provide the standard mechanism in C# and .NET for notifying any interested parties of important actions.

Tip

The event pattern is so common that it has a name: the Observer design pattern. It's one of many common design patterns that people have published for anyone to use in their own code. To begin learning about other design patterns, you can consult Design Patterns For Dummies, by Steve Holzner.

The Observer pattern consists of an Observable object — the object with interesting events (sometimes called the Subject, though that confuses me) — and any number of Observer objects: those interested in a particular event. The observers register themselves with the Observable in some way and, when events of interest occur, the Observable notifies all registered observers. You can implement this pattern in numerous ways without events (such as callbacks and interfaces), but the C# way is to use events.

Tip

An alternative name for observers that you may encounter is listeners. Listeners "listen" for events. And that's not the last of the alternatives.

What's an event? Publish/Subscribe

One analogy for events is your local newspaper. You and many other people contact the paper to subscribe, and then the paper delivers current newspapers to you, typically soaked in a rain puddle. The newspaper company is the Publisher, and its customers are Subscribers, so this variation of Observer is often called Publish/Subscribe. That's the analogy I stick to in this chapter, but keep in mind that the Observer pattern is the Publish/Subscribe pattern with different terminology. Observers are subscribers, and the Observable object that they observe is a publisher.

In C#, when you have a class on which interesting events arise, you advertise the availability of notifications to any classes that may have an interest in knowing about such events by providing an event object (usually public).

Note

The term event has two meanings in C#. You can think of the word event as meaning both "an interesting occurrence" and a specific kind of C# object. The former is the real-world concept, and the latter is the way it's set up in C#, using the event keyword.

How a publisher advertises its events

Note

To advertise for subscribers, a class declares a delegate and a corresponding event, something like this:

public class PublisherOfInterestingEvents
{
  // A delegate type on which to base the event.
  // Should be declared 'internal' if all subscribers are in the same assembly.
  public delegate void NewEditionEventHandler(object sender,
                                              NewEditionEventArgs e);
  // The event:
  public event NewEditionEventHandler NewEdition;
  // ... other code.
}

The delegate and event definitions announce to the world: "Subscribers welcome!" You can think of the NewEdition event as similar to a variable of the NewEditionEventHandler delegate type. (So far, no events have been sent. This is just the infrastructure for them.)

Tip

It's considered good practice to append EventHandler to the name of a delegate type that is the basis for events.

Note

A common example, which you can see in the SimpleProgress code example, discussed earlier in this chapter, is a Button advertising its various events, including a Click event. In C#, class Button exposes this event as

event _dispCommandBarControlEvents_ClickEventHandler Click;

where the second, lo-o-ong item is a delegate defined somewhere in .NET.

Note

Because events are used so commonly, .NET defines two event-related delegate types for you, named EventHandler and EventHandler<TEventArgs>. You can change NewEditionEventHandler in the previous code to EventHandler or to the generic EventHandler<TEventArgs>, and you don't need your own delegate type. Throughout the rest of this chapter, I pretend that I used the built-in EventHandler<TEventArgs> delegate type mentioned earlier, not EventHandler or my custom type, NewEditionEventHandler. You should prefer this form, too:

event EventHandler<NewEditonEventArgs> NewEdition;

Note

The NewspaperEvents example on the Web site demonstrates correctly setting up your event and handling it in various subscribers. (A second sample program, NewspaperEventsNongeneric, avoids the generic stuff if you get <ahem> code feet. If so, you can mentally omit the <eventhandlerargs> information in the following section.)

How subscribers subscribe to an event

To receive a particular event, subscribers sign up something like this:

publisher.EventName +=
  new EventHandler<some EventArgs type here >(some method name here);

where publisher is an instance of the publisher class, EventName is the event name, and EventHandler<TEventArgs> is the delegate underneath the event. More specifically, the code in the previous example might be:

myPublisher.NewEdition += new EventHandler<NewEditionEventArgs>(MyPubHandler);

Because an event object is a delegate under its little hood, the += syntax is adding a method to the list of methods that the delegate will call when you invoke it.

Tip

Any number of objects can subscribe this way (and the delegate will hold a list of all subscribed "handler" methods) — even the object on which the event was defined can subscribe, if you like. (And yes, this example shows that a delegate can "point" to more than one method.)

Note

In the SimpleProgress program, look in the Form1.Designer.cs file for how the form class registers itself as a subscriber to the buttons' Click events.

How to publish an event

When the publisher decides that something worthy of publishing to all subscribers has occurred, it raises (sends) the event. This situation is analogous to a real newspaper putting out the Sunday edition.

To publish the event, the publisher would have code like this in one of its methods (but see the later section "A recommended way to raise your events"):

NewEditionEventArgs e = new NewEditionEventArgs(<args to constructor here>);
NewEdition(this, e);  // Raise the event -- 'this' is the publisher object.

Or for the Button example, though this is hidden in class Button:

EventArgs e = new EventArgs();  // See next section for more on this topic.
Click(this, e);                 // Raise the event.

In each of these examples, you set up the necessary arguments — which differ from event to event; some events need to pass along a lot of info. Then you raise the event by "calling" its name (like invoking a delegate!):

eventName(<argumentlist>); // Raising an event (distributing the newspaper)
NewEdition(this, e);

Note

Events can be based on different delegates with different signatures, that have different parameters, as in the earlier NewEditionEventHandler example, but providing the sender and e parameters is conventional for events. The built-in EventHandler and EventHandler<TEventArgs> delegate types define them for you.

Passing along a reference to the event's sender (the object that raises the event) is useful if the event-handling method needs to get more information from it. Thus a particular Button object, button1, can pass a reference to the Form class the button is a part of. The button's Click event handler resides in a Form class, so the sender is the form: You would pass this.

Note

You can "raise" an event in any method on the publishing class. But when? Raise it whenever appropriate. I have a bit more to say about raising events after the next section.

How to pass extra information to an event handler

The e parameter to an event handler method is a custom subclass of the System.EventArgs class. You can write your own NewEditionEventArgs class to carry whatever information you need to convey:

public class NewEditionEventArgs : EventArgs
{
  public NewEditionEventArgs(DateTime date, string majorHeadline)
  { PubDate = date; Head = majorHeadline; }
  public DateTime PubDate { get; private set; }  // Compiler creates details.
  public string Head { get; private set; }  //
}

You should implement this class's members as properties, as shown in the previous code example. The constructor uses the private setter clauses on the properties.

Often, your event doesn't require any extra arguments and you can just fall back on the EventArgs base class, as shown in the next section.

Tip

If you don't need a special EventArgs-derived object for your event, just pass:

NewEdition(this, EventArgs.Empty);  // Raise the event.

A recommended way to raise your events

The earlier section "How to publish an event" shows the bare bones of raising an event. However, I recommend that you always define a special "event raiser" method, like this:

protected virtual void OnNewEdition(NewEditionEventArgs e)
{
  EventHandler<NewEditionEventArgs> temp = NewEdition;
  if(temp != null)
  {
    temp(this, e);
  }
}

Providing this method ensures that you always remember to complete two steps:

  1. Store the event in a temporary variable.

    Note

    This step makes your event more usable in situations where multiple "threads" try to use it at the same time — threads divide your program into a foreground task and one or more background tasks, which run simultaneously (concurrently). I don't cover how to write multithreaded programs in this book; just follow this guideline.

  2. Check the event for null before you try to raise it.

    If it's null, trying to raise it causes an error. Besides, null also means that no other objects have shown an interest in your event (none is subscribed), so why bother raising it? Always check the event for null, regardless of whether you write this OnSomeEvent method.

Making the method protected and virtual allows subclasses to override it. That's optional.

After you have that method, which always takes the same form (making it easy to write quickly), you call the method when you need to raise the event:

void SomeMethod()
{
  // Do stuff here and then:
  NewEditionEventArgs e =
    new NewEditionEventArgs(DateTime.Today, "Peace Breaks Out!");
  OnNewEdition(e);
}

How observers "handle" an event

The subscribing object specifies the name of a handler method when it subscribes — it's the argument to the constructor (boldfaced):

button1.Click += new EventHandler<EventArgs>(button1_Click);

This line sort of says, "Send my paper to this address, please." Here's a handler for the NewEdition event:

myPublisher.NewEdition += new EventHandler<NewEditionEventArgs>(NewEdHandler);
...
void NewEdHandler(object sender, NewEditionEventArgs e)
{
  // Do something in response to the event.
}

For example, a BankAccount class can raise a custom TransactionAlert event when anything occurs in a BankAccount object, such as a deposit, withdrawal, or transfer or even an error. A Logger observer object can subscribe and log these events to a file or a database as an audit trail.

Tip

Note

When you create a button handler in Visual Studio (by double-clicking the button on your form), Visual Studio generates the subscription code in the Form1.Designer.cs file. You shouldn't edit the subscription, but you can delete it and replace it with the same code written in your half of the partial form class. Thereafter, the form designer knows nothing about it.

In your subscriber's handler method, you do whatever is supposed to happen when your class learns of this kind of event. To help you write that code, you can cast the sender parameter to the type you know it to be:

Button theButton = (Button)sender;

and then call methods and properties of that object as needed. Because you have a reference to the sending object, you can ask the subscriber questions and carry out operations on it if you need to — like the person who delivers your newspaper knocking on your door to collect the monthly subscription fees. And, you can extract information from the e parameter by getting at its properties in the same way:

Console.WriteLine(e.HatSize);

You don't always need to use the parameters, but they can be handy.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.162.76