Events 2020
An event in C# is a way for a class to provide notifications to clients of that class when some interesting thing happens to an object.
The most familiar use for events is in graphical user interfaces; typically, the classes that represent controls in the interface have events that are notified when the user does something to the control (for example, click a button).
Events are declared using delegates.
As a reminder, a delegate will allow us to specify what the function we'll be calling looks like without having to specify which function to call. The declaration for a delegate looks just like the declaration for a function, except that in this case, we're declaring the signature of functions that this delegate can reference.
Here is a summary of delegate declaration:
-
public delegate void SimplestDelegate()
This declaration defines a delegate named SimplestDelegate, which will encapsulate any method that takes no parameters and returns no value.
-
public delegate int SimpleDelegate(object obj1, object obj2)
This declaration defines a delegate named SimpleDelegate , which will encapsulate any method that takes two objects as parameters and returns an int.
The Event model in C# finds its roots in the event programming model that is popular in asynchronous programming. The basic foundation behind this programming model is the idea of publisher (broadcaster, source) and subscribers (sink). In this model, we have publishers who will do some logic and publish (fire) an event. Publishers will then send out their event only to subscribers who have subscribed to receive the specific event:
- publisher is a type that contains a delegate field. The publisher decides when to publish, by invoking the delegate.
- subscribers are the method target recipients. A subscriber decides when to start and stop listening, by calling += and -= on the publisher's delegate. A subscriber does not know about, or interfere with, other subscribers.
To declare an event member, we put the event keyword in front of a delegate member. For example:
public class MyClass { public delegate int SimpleEventHandlerDelegate(object obj1, object obj2) public event SimpleEventHandlerDelegate SimpleEvent; }
Here, event is the Keyword, SimpleEventHandlerDelegate is the Delegate Type, and the SimpleEvent is the Name of Event.
An Event Is a Member:
A common error is to think of an event as a type. But an event is not a type. An event is a member, and there are several ramifications to this:
- Because a member is not a type, we do not use an object-creation expression ( a new expression) to create its object.
- Because an event is a member
- It must be declared in a class with the other members.
- We cannot declare an event in a block of executable code.
- An event member is implicitly and automatically initialized to null.
Code within the MyClass type has full access to SimpleEvent and can treat it as a delegate. Code outside of MyClass can only perform += and -= operations on SimpleEvent
While a delegate object encapsulates a method so that it can be called anonymously, an event is a way for a class to allow clients to give it delegates to methods that should be called when the event occurs. When the event occurs, the delegate(s) given to it by its clients are invoked.
Here is the general structure of the publisher and subscriber.
In the following example, the name NumberChangedEventHandler indicates that this delegate is used to publish an event notifying subscribers that the value of a certain number they are monitoring has changed:
public delegate void NumberChangedEventHandler(int number); public class MyPublisher { public NumberChangedEventHandler NumberChanged; // Other methods and members } public class MySubscriber { public void OnNumberChanged(int number) { ... } }
The compiler replaces the delegate type definition with a sophisticated class providing the implementation of the subscriber list as we discussed in the Delegates.
Let's look at more specific example:
using System; namespace Events { // An event declaration requires the name of a "delegate type" public delegate void ProgressChangedHandler(int x, int y); public class ProgressBar { int percent; int old_percent; public ProgressBar(int pct) { this.percent = pct; } // The event we publish public event ProgressChangedHandler ProgressChanged; // The method which fires the Event public int Progress { get { return percent; } set { if (percent == value) return; if (ProgressChanged != null) // fires the event ProgressChanged(percent, value); old_percent = percent; percent = value; } } } class TestEvent { static void Main(string[] args) { ProgressBar bar = new ProgressBar(10); bar.Progress = 30; // register with the ProgressChanged event bar.ProgressChanged += bar_ProgressChanged; // value doesn't change, no event bar.Progress = 30; // value changes, fires event bar.Progress = 60; } static void bar_ProgressChanged(int x, int y) { Console.WriteLine("bar_ProgressChanged() event"); Console.WriteLine("Progressed from {0} to {1}", x, y); } } }
Output is:
bar_ProgressChanged() event Progressed from 30 to 60
An Event Has a Private Delegate:
An event contains a private delegate. The important things to know about an event's private delegate are the following:
- An event gives structured access to its privately controlled delegate.
- Unlike the many operations available with a delegate, with an event, we can only add, remove, and invoke event handlers.
- When an event is raised, it invokes the delegate, which sequentially calls the methods in the invocation list.
So, if we remove the eventkeyword from our example so that ProgressChanged becomes an ordinary delegate field:
public event ProgressChangedHandler ProgressChanged; => public ProgressChangedHandler ProgressChanged;
our example still gives the same results. However, ProgressBar would be less robust, in that subscribers could do the following thins to interfere with each other:
- Replace other subscribers by reassigning ProgressChanged (instead of using += operator).
- Clear all subscribers by setting ProgressChanged to null.
- Broadcast (Publish) to other subscribers by invoking the delegate.
GUI programming is event driven. That means that while the program is running, it can be interrupted at any time by events such as button clicks or system timers. When this happens, the program needs to handle the event and to continue on its course.
Clearly, this asynchronous handling of program events is the perfect situation to use C# events, Window GUI programming uses events so extensively that there is a standard .NET Framework pattern for using them.
The foundation of the standard pattern for event is the EventHandler delegate type, which is declared in the System namespace. At the core of the event pattern is System.EventArgs: a predefined Framework class with no members other than the static Empty property. EventArgs is a base class for conveying information for an event.
In our ProgressBar example, we would subclass EventArgs to convey the old and new percents when a ProgressChanged event is fired:
public class ProgressChangedEventArgs : System.EventArgs { public readonly int LastPercent; public readonly int NewPercent; public ProgressChangedEventArgs(int lastPct, int newPct) { LastPercent = lastPct; NewPercent = newPct; } }
For reusability, the EventArgs subclass is named according to the information it contains rather than the event for which it well be used. It typically exposed data as properties or as read-only fields.
With an EventArgs subclass in place, the next step is to choose a delegate for the event.
The declaration of the EventHandler delegate type is:
public delegate void EventHandler(object sender, EventArgs e);
- object sender
This first parameter is meant to hold a reference to the object that raised the event. It is type of object and can, therefore, match any instance of any type. - EventArgs e
This second parameter is meant to hold state information of whatever type is appropriate for the application. - void
The return type is void. - The delegate name must end in EventHandler.
The Framework defines a generic delegate called Systenm.EventHanlder<> that satisfies there rules:
public delegate void EventHandler<TEventAtgs> (object source, TEventArgs e) where TEventArgs : EventArgs;
The next step is to define an event of the chosen delegate type. Here, we use the generic EventHandler delegate:
public class ProgressBar { ... // The event we publish public event EventHandler<ProgressChangedEventArgs> ProgressChanged; ... }
As a final step, the pattern requires that we write a protected virtual method that fires the event. The name must match the name of the event, prefixed with the word On, and then accept a single EventArgs argument:
public class ProgressBar { ... // The event we publish public event EventHandler<ProgressChangedEventArgs> ProgressChanged; ... protected virtual void OnProgressChanged(ProgressChangedEventArgs e) { if (ProgressChanged != null) ProgressChanged(this, e); } }
Here is the complete example code:
using System; namespace Events { public class ProgressBar { int percent; public ProgressBar(int pct) { this.percent = pct; } // The event we publish public event EventHandler<ProgressChangedEventArgs> ProgressChanged; // The method which fires the Event public int Progress { get { return percent; } set { if (percent == value) return; OnProgressChanged(new ProgressChangedEventArgs(percent, value)); percent = value; } } protected virtual void OnProgressChanged(ProgressChangedEventArgs e) { if (ProgressChanged != null) ProgressChanged(this, e); } } public class ProgressChangedEventArgs : System.EventArgs { public readonly int LastPercent; public readonly int NewPercent; public ProgressChangedEventArgs(int lastPct, int newPct) { LastPercent = lastPct; NewPercent = newPct; } } class TestEvent { static void Main(string[] args) { ProgressBar bar = new ProgressBar(10); bar.Progress = 30; // register with the ProgressChanged event bar.ProgressChanged += bar_ProgressChanged; // value doesn't change, no event bar.Progress = 30; // value changes, fires event bar.Progress = 60; } static void bar_ProgressChanged(object sender, ProgressChangedEventArgs e) { Console.WriteLine("bar_ProgressChanged() event"); Console.WriteLine("Progressed from {0} to {1}", e.LastPercent, e.NewPercent); } } }
Output is the same as the previous example:
bar_ProgressChanged() event Progressed from 30 to 60
Let's talk more about EventArgs in
public delegate void EventHandler(object sender, EventArgs e);
We may be tempted to think that, since the second parameter is meant for passing data, an EventArgs class object would be able to store data of some sort. We would be wrong:
- The EventArgs class is designed to carry no data. It is used for event handlers that do not need to pass data. So, it is generally ignored by the event handlers.
- If we want to pass data, we must declare a class derived from EventArgs, with the appropriate fields to hold the data we want to pass. (as in the previous example)
Even though the EventArgs class does not actually pass data, it is important part of the pattern of using the EventHandler delegate. Class object and class EventArgs are the base classes for whatever actual types are used as the parameters. This allows EventHandler to provide a signature that is the lowest common denominator for all events and event handlers, allowing them to have exactly two parameters, rather than having different signatures for each case.
In the following example, the predefined nongeneric EventHandler delegate can be used when an event doesn't carry extra information. In the example, we rewrite ProgressBar such that the ProgressChanged event is fired after the progress changes, and no information about the event is necessary, other than it happened. We also make use of the EventArgs.Empty property, in order to avoid unnecessary instantiating an instance of EventArgs as in:
using System; namespace Events { public class ProgressBar { int percent; public ProgressBar(int pct) { this.percent = pct; } // The event we publish public event EventHandler ProgressChanged; // The method which fires the Event public int Progress { get { return percent; } set { if (percent == value) return; percent = value; OnProgressChanged(EventArgs.Empty); } } protected virtual void OnProgressChanged(EventArgs e) { if (ProgressChanged != null) ProgressChanged(this, e); } } class TestEvent { static void Main(string[] args) { ProgressBar bar = new ProgressBar(10); bar.Progress = 30; // register with the ProgressChanged event bar.ProgressChanged += bar_ProgressChanged; // value doesn't change, no event bar.Progress = 30; // value changes, fires event bar.Progress = 60; } static void bar_ProgressChanged(object sender, EventArgs e) { Console.WriteLine("bar_ProgressChanged() event"); } } }
This time, because we don't carry any information on the percent (we don't subclass from EventArgs), we just get informed that the event happened.
bar_ProgressChanged() event
An event's accessors are the implementations of its += and -= functions. By default, accessors are implemented implicitly by the compiler. Consider this event declaration:
public event EventHandler ProgressChanged;
The compiler converts this to the following:
- A private delegate field
- A public pair of event accessor functions, whose implementations forward the += and -= operations to the private delegate field
We can take over this process by defining explicit event accessors. Here's a manual implementation of the ProgressChanged event from our previous example:
// declaring a private delegate private EventHandler _ProgressChanged; public event EventHandler ProgressChanged { add {_ProgressChanged += value; } remove {_ProgressChanged -= value; } }
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization