Event - Expert C# 5.0: with .NET 4.5 Framework (2013)

Expert C# 5.0: with .NET 4.5 Framework (2013)

CHAPTER 8

images

Event

This chapter will discuss the event program. A type that defines an event member allows other types to subscribe for the notification when something happened in the type. We start by designing a type, which, when exposed to the event where subscribers are to subscribe, implements the code to handle the event and explains the concept of the event in C# language. We will then explore the behind the scenes workings of the event, for example, how C# compiler translates an event member to implement, add, and remove a method. We also examine how the subscription operator, for example, += and -=, will be translated to use the add and remove method compiled for the event member by the C# compiler. Finally, we will also explore how different pieces of the event program fit together in runtime.

Understanding the Event

In .NET, an event is a mechanism for a class, for example, if you consider a class named EC, that provides notifications to the subscribers of that class when something happens to an object of that EC. The most common use for events is in graphical user interfaces. Typically the classes that represent controls in the interface have events that are notified, for example, when the user clicks a button control. In Figure 8-1, you can see an event that is typically exposed for the subscription (S1, S2....Sn, where n is a valid range of subscribers) where subscribers subscribe to be notified. When the event is raised from the event handler class, it will notify the subscribers by sending the notification, as demonstrated in Figure 8-1.

images

Figure 8-1. Example of a basic event.

Figure 8-1 shows the basics of the event in C#. It can be used to provide a generally useful way for objects, for example, the event handler, to notify clients of the state changes, which may be useful for the subscribers of that object.

Events are declared using delegates (Chapter 7). A delegate object encapsulates a method (typically called the callback method) so that it can be called on later from the appropriate places when needed. In C#, the event provides a way for a class to allow subscribers to provide delegates that should be called when the event occurs. When the event occurs, the event handler executes the delegate(s) given to it by its subscribers.

In the following sections, we will explore how to design a type with an event, how the C# compiler takes care of the event, and how the CLR executes an event in runtime.

Designing a Type with the Event

Defining a type that exposes one or more event member requires a few steps. First, define a field in a type with EventHandler or EventHandler<TEventArgs> type followed by the event keyword. This requires defining a handler method in that type that exposes events. This handler will execute when an event is raised by another method from the event handler class. In Listing 8-1, NewsManager type exposes an event member NewsEvent that will be used by the subscribers to subscribe for the notification by adding relevant methods with it as the callback method. This callback will be executed by the event handler when there is any event raised in the NewsManager type. So the subscribers will be notified when the event is raised in the NewsManager type. The subscriber of the event adds relevant method(s) to the event.

Listing 8-1 presents an example of the event declaration in a program. In the example, NewsManager class exposes an event and implements a method, OnNewsArrival, that is used to handle the event raised by the PublishNews. We will explore this in greater detail shortly. Two subscriber classes—Reviewer and Publisher—are used to show subscribers that used subscribed for the notification in the NewsEvent event of the NewsManager class.

Let’s look at the skeleton of a program, which declares an event, a subscriber, and an event argument class to pass additional information to the subscriber as part of the event notification.

Listing 8-1. Skeleton of the Event Declaration

namespace Ch08
{
/* Event initialization and setup */
class Program
{
static void Main(string[] args) {}
}

/* Subscribers of the event. When these classes subscribed to the
* event, it passes an instance of the EventHandler instantiated
* using the relevant function pointer of
* the method it uses as callback.*/
public class Reviewer {}
public class Publisher {}

/* This is the class which exposes the event that contains all
* the subscribed methods for the event, handle the event when
* it occurs and send the notification the
* subscribed methods to notify them about the event. */
public class NewsManager
{
public event EventHandler<NewsEventArgs> NewsEvent;
public void PublishNews(string name, string detail) {...}
protected virtual void OnNewsArrival(NewsEventArgs args){...}
}

/* It is the event argument which uses to pass additional information
* from the event to subscribers.*/
public class NewsEventArgs : EventArgs {}
}

In the following sections, you will learn more about the steps mentioned in Listing 8-1 that were used to define the event in a program.

EventHandler Declaration

The C# event keyword is used to define an event member in a type by giving the accessibility as public to expose the other types to consume it. A type of delegate, for example, EventHandler<TEventArgs> (derived from the delegate), comes after the event keyword to indicate the prototype of the method(s) and any valid identifier used as a name of the event. The event member NewsEvent is defined for the NewsManager class, as shown in the code:

public event EventHandler<NewsEventArgs> NewsEvent;

The name of the event is NewsEvent with the type of EventHandler<NewsEventArgs>. The subscribers of the event (to be notified) must provide a callback method whose prototype matches that of the EventHandler<NewsEventArgs> delegate type. The EventHandler<NewsEventArgs> is the generic System.EventHandlerdelegate defined as follows:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

The EventHandler<TEventArgs> type is defined in the System namespace in the mscorlib.dll assembly, as shown in Figure 8-2.

images

Figure 8-2. EventArgs, EventHandler, and EventHandler`1<TEventArgs>

UNDERSTANDING `, !, AND !! SYMBOL IN THE TYPE NAME

1. If a type C has one or more generic parameters, the C# compiler compiles the name of the type C with the suffix `n, where n is a decimal integer constant (without leading zeros) representing the number of generic parameters that C has.

2. The generic parameters of a generic type definition are referred to by their index. Generic parameter zero is referred to as !0, generic parameter one as !1, and so on.

3. In the body of a generic method definition, its generic parameters are referred to by their index; generic parameter zero is referred to as !!0, generic parameter one as !!1, and so on.

For example in C#:

public class GenericType<T, R>
{
public T Method1(T a, R b) { return a; }
public N GenericMethod<M, N>(M c, N d) { return d; }
public N GenericMethod<M, N, P>(M c, N d, P e) { return d; }
}
public class GenericType<T, R, S> { }

The GenericType<T,R> and GenericType<T, R, S> classes are compiled into IL as:

/* '2 - ' symbol postfix with the number of parameter (2) defined in
* the C# code */
.class public auto ansi beforefieldinit ConsoleApplication2.GenericType'2<T,R>
extends [mscorlib]System.Object
{
.method public hidebysig instance !T Method1(!T a,!R b) cil
managed {..}
.method public hidebysig instance !!N GenericMethod<M,N>(!!M c,!!N d) cil
managed {..}
.method public hidebysig instance !!N GenericMethod<M,N,P>
(!!M c,!!N d,!!P e) cil managed {..}
}

/* '3 - ' symbol postfix with the number of parameter (3) defined
* in the C# code */
.class public auto ansi beforefieldinit
ConsoleApplication2.GenericType'3<T,R,S>
extends [mscorlib]System.Object {..}

The EventHandler<TEventArgs> is derived from the MulticastDelegate class, and it has the definition as shown in Listing 8-2.

Listing 8-2. The IL Definition of the EventHandler<TEventArgs> Class

/* The EventHandler< TEventArgs> derived from the MulticastDelegate
* class which derived from the Delegate class. */
.class public auto ansi serializable sealed EventHandler<TEventArgs>
extends System.MulticastDelegate
{
.method public hidebysig specialname rtspecialname instance void
.ctor(object 'object', native int 'method') runtime managed {}

.method public hidebysig newslot virtual instance class System.IAsyncResult
BeginInvoke
(object sender, !TEventArgs e, class System.AsyncCallback callback,
object 'object') runtime managed {}

.method public hidebysig newslot virtual instance void
EndInvoke(class System.IAsyncResult result) runtime managed {}

/* The Invoke method will be used to start executing the methods
* subscribed to the event to get the notification from the event.
* This method will be called from the handler method of
* the NewsManager.*/
.method public hidebysig newslot virtual instance void
Invoke(object sender, !TEventArgs e) runtime managed {}
}

Listing 8-2 shows an Invoke, which is used by the event handler to send notification to the subscriber when an event is raised.

Event Argument

If you want to pass additional information from the object that raised the event to the subscribers object, you need to define a type, which encapsulates this additional information. This type is typically called the event argument class. When you define this type, you add private fields to encapsulate the additional information you want to pass and expose this additional information to the subscribers via the read-only public fields.

A class that holds additional information to pass to the event handler should be derived from System.EventArgs, which is defined in the System namespace of the mscorlib.dll assembly, as shown in Figure 8-2. Listing 8-3 shows the definition of the EventArgs class.

Listing 8-3. The Class Definition of the EventArgs

public class EventArgs
{
public static readonly EventArgs Empty;
static EventArgs();
public EventArgs();
}

The name of the event argument class should be suffixed with EventArgs. In this example, the NewsEventArgs class has fields identifying the title of the news (Title) and the details of the news (Detail), as shown in Listing 8-4.

Listing 8-4. Example of the NewsEventArgs Class

public class NewsEventArgs : EventArgs
{
/* Declared few private fields */
private string title;
private string detail;

public NewsEventArgs(string TitleOfTheNews, string DetailOfTheNews)
{
title = TitleOfTheNews;
detail = DetailOfTheNews;
}

/* ReadOnly fields */
public string Title { get { return title; } }
public string Detail { get { return detail; } }
}

Event Handler

The class that defines and exposes an event is responsible to define an event handler for the event to handle when an event is raised. This method will be executed in response to an event, for example, in Listing 8-5, the OnNewsArrival method is responsible for handling the event for the NewsManagerclass when an event is raised by the PublishNews method.

In general, the event handler method can be defined as a protected, virtual method that is called by code internally within the class and its derived classes when the event is to be raised. This method takes one parameter, a NewsEventArgs object, as the event argument, which includes the additional information passed to the objects receiving the notification. The default implementation of this method simply checks if any objects have registered interest in the event and, if so, the event will be raised, thereby notifying the registered methods that the event has occurred. Let’s look at the event handler class, NewsManager class, as shown in Listing 8-5.

Listing 8-5. Example of the Event Handler Class

public class NewsManager
{
/* An container of the subscribed method to the event. Clients
* can subscribe for the notification via NewsEvent event.*/
public event EventHandler<NewsEventArgs> NewsEvent;

public void PublishNews(string name, string detail)
{
NewsEventArgs na = new NewsEventArgs(name, detail);

/* If news arrived and ready to publish then call OnNewsArrival
* method which will execute subscribed methods.*/
OnNewsArrival(na);
}

/* If anyone subscribe for the notification then this method will
* invoke each of the subscribed method and execute all. */
protected virtual void OnNewsArrival(NewsEventArgs args)
{
EventHandler<NewsEventArgs> newsHandler = NewsEvent;
if (newsHandler != null)
{
newsHandler(this,args);
}
}
}

In the NewsManager class, the OnNewsArrival method is called from the PublishNews method to indicate that a new news message has arrived in the NewsManager class. The PublishNews method accepts information about the message and constructs a NewsEventArgs object, passing the message information to its constructor, and NewsManager’s own virtual OnNewsArrival method is then called to formally notify the NewsManager object of the new news message. Typically this is called the event raised, and it notifies all of the subscribed methods that need to be notified.

Subscriber

This section will explore the subscriber, who has asked for subscription of the NewsEvent of the NewsManager class. The Reviewer class subscribes to the event in the constructor by providing the callback method ReviewOnArrivedNews. It also implements the UnSubscribe method to allow the subscriber to unsubscribe from the event.

In Listing 8-6, the Reviewer class subscribed to the event using the += operator from the constructor by passing the ReviewOnArrivedNews method as the callback method. The ReviewOnArrivedNews method has the same prototype as the NewsEvent event defined in the NewsManager class, shown in Listing 8-5. As mentioned earlier, the NewsEvent is the type of EventHandler<NewsEventArgs> delegate whose signature shows that it accepts two input parameters, such as sender as the type of object and e as the type of TEventArgs. In addition, the ReviewOnArrivedNews method has the same signature as the EventHandler<NewsEventArgs>, so it can be added or removed from the NewsEvent event.

Listing 8-6. Example of the Subscribers Class

public class Reviewer
{
public Reviewer(NewsManager nlm)
{
/* Subscribe to the NewsManager for the notification.*/
nlm.NewsEvent += ReviewOnArrivedNews;
}

/* When news arrived if subscribe then execute this method.*/
private void ReviewOnArrivedNews(object sender,NewsEventArgs na)
{
Console.WriteLine("Reviewed:\n{0},\t{1}", na.Title, na.Detail);
}

/* To unsubscribe from the NewsEvent */
public void UnSubscribe(NewsManager nlm)
{
nlm.NewsEvent -= ReviewOnArrivedNews;
}
}

Listing 8-6 also shows the UnSubscribe method, which uses the -= operator to unsubscribe the ReviewOnArrivedNews method from the NewsEvent event. And in the Reviewer constructor, the ReviewOnArrivedNews method was subscribed using the += operator to the NewsEvent event. As described earlier, when we want to add and remove a callback method from the NewsEvent event, the callback method has to have the same signature as EventHandler<NewsEventArgs>. The ReviewOnArrivedNews method has the same signature as EventHandler<NewsEventArgs>, so it can be removed from the NewsEvent. Listing 8-6 shows theReviewer class to indicate the subscriber implementation. The Publisher class will have almost the same implementation, as shown in Listing 8-8.

Execute the Defined Event

The Program class instantiates an instance of the NewsManager class and passes it to the subscriber’s class to let subscribers subscribe for the notification. The Program class then calls the PublishNews method of the NewsManager class to raise two events, as shown in Listing 8-7.

Listing 8-7. Example of the Event Executor

class Program
{
static void Main(string[] args)
{
NewsManager nlm = new NewsManager();
/* Initialize the client of the news manager event. The
* clients will subscribe to the event in the initialization time.*/
/* Reviewer class defined in the Listing 8-8 */
Reviewer subscriberOne = new Reviewer(nlm);

/* Publisher class defined in the Listing 8-8 */
Publisher subscriberTwo = new Publisher(nlm);

/* Some news arrived to the NewsManager to publish and notify
* to the subscribers.*/
nlm.PublishNews("Higgs particle",
"The Higgs particle is named after Peter Higgs.");
nlm.PublishNews("Expert C# 5.0 with .NET Framework 4.5",
"A about the C# language.");

/* Finished job so UnSubscribe the events */
subscriberOne.UnSubscribe(nlm);
subscriberTwo.UnSubscribe(nlm);

/* Publishing new news but it not going to be notified as
* subscriberOne and subscriberTwo already unsubscribes
* by calling the UnSubscribe method */
nlm.PublishNews("10th Dimensional world",
"Still under investigation so don't publish.");
}
}

In Listing 8-7, the UnSubscribe method is called twice for the subscribers—subscriberOne and subscriberTwo—to unsubscribe for the notification. We saw earlier the different pieces of the program that are used to explain the event. In Listing 8-8, the full listing of the program is shown.

Listing 8-8. Full Listing of the Event Program

using System;

namespace Ch08
{
class Program
{
static void Main(string[] args)
{
NewsManager nlm = new NewsManager();
/* Initialize the client of the news manager event. The
* clients will subscribe to the event in the
* initialization time.*/
Reviewer subscriberOne = new Reviewer(nlm);
Publisher subscriberTwo = new Publisher(nlm);

/* Some news arrived to the NewsManager to publish and
* notify to the subscribers.*/
nlm.PublishNews("Higgs particle",
"The Higgs particle is named after Peter Higgs.");
nlm.PublishNews("Expert C# 5.0 with .NET Framework 4.5",
"A about the C# language.");

/* Finished job so UnSubscribe the events */
subscriberOne.UnSubscribe(nlm);
subscriberTwo.UnSubscribe(nlm);

/* Publishing new news but it not going to be notified */
nlm.PublishNews("10th Dimensional world",
"Still under investigation so don't publish.");
}
}

public class Reviewer
{
public Reviewer(NewsManager nlm)
{
/* Subscribe to the NewsManager for the notification.*/
nlm.NewsEvent += ReviewOnArrivedNews;
}

/* When news arrived if subscribe then execute this method.*/
private void ReviewOnArrivedNews(object sender, NewsEventArgs na)
{
Console.WriteLine("Reviewed:\n{0},\t{1}", na.Title, na.Detail);
}

/* To unsubscribe from the NewsEvent */
public void UnSubscribe(NewsManager nlm)
{
nlm.NewsEvent -= ReviewOnArrivedNews;
}
}

public class Publisher
{
public Publisher(NewsManager nlm)
{
/* Subscribe to the NewsManager for the notification.*/
nlm.NewsEvent += PublishArrivedNews;
}

/* When news arrived if subscribe then execute this method.*/
private void PublishArrivedNews(object sender, NewsEventArgs na)
{
Console.WriteLine("Published:\n{0} news.", na.Title);
}

public void UnSubscribe(NewsManager nlm)
{
nlm.NewsEvent -= PublishArrivedNews;
}
}

public class NewsManager
{
/* An container of the subscribed method to the event.
* Clients can subscribe for the notification via
* NewsEvent event.*/
public event EventHandler<NewsEventArgs> NewsEvent;

public void PublishNews(string name, string detail)
{
NewsEventArgs na = new NewsEventArgs(name, detail);

/* If news arrived and ready to publish then call OnNewsArrival
* method which will execute subscribed methods.*/
OnNewsArrival(na);
}

/* If anyone subscribe for the notification then this method will
* invoke each of the subscribed method and execute all. */
protected virtual void OnNewsArrival(NewsEventArgs args)
{
EventHandler<NewsEventArgs> newsHandler = NewsEvent;
if (newsHandler != null)
{
newsHandler(this, args);
}
}
}

public class NewsEventArgs : EventArgs
{
/* Declared few private fields */
private string title;
private string detail;

public NewsEventArgs(string TitleOfTheNews, string DetailOfTheNews)
{
title = TitleOfTheNews;
detail = DetailOfTheNews;
}

/* ReadOnly fields */
public string Title { get { return title; } }
public string Detail { get { return detail; } }
}
}

This program will produce the following output:

Reviewed:
Higgs particle, The Higgs particle is named after Peter Higgs.
Published:
Higgs particle news.
Reviewed:
Expert C# 5.0 with .NET Framework 4.5, A about the C# language.
Published:
Expert C# 5.0 with .NET Framework 4.5 news.

Behind the Scenes

When the C# compiler compiles the code presented in Listing 8-8, it will regenerate the NewsManager class and event subscribers Publisher and Reviewer classes. The event member of the NewsManager class will be implemented to contain add_<EventName> and remove_<EventName> (<EventName> will be replaced with the relevant event name, for example, add_<EventName> will be add_NewsEvent and the remove_<EventName> method will be remove_NewsEvent) methods to add and remove subscribed method to and from the event.

The subscriber’s += and -= operators will be replaced with the add_<EventName> and remove_<EventName> method in the Publisher and Reviewer classes. In the following sections, we will explore more about these by looking into the decompiled IL code (decompiled using the .NET Reflector tool) for the code given in Listing 8-8.

In Compile Time

The C# compiler compiles the NewsManager class to generate the add_<EventName> and remove_<EventName> methods for the event member NewsEvent declared in the NewsManager class. These methods are also used by the subscribers to subscribe for the notification of the event, which you will see later in Listing 8-10. Listing 8-9 shows the decompiled IL version of the NewsManager class.

Listing 8-9. IL Version of the NewsManager

.class public auto ansi beforefieldinit NewsManager extends [mscorlib]System.Object
{
/* The C# compiler translates NewsEvent into two methods add_NewsEvent
* and remove_NewsEvent which will used by the CLR to add subscribed
* method into the event using the add_NewsEvent method and also methods
* can be unsubscribed from the event using the remove_NewsEvent method */
.event [mscorlib]System.EventHandler'1<class Ch08.NewsEventArgs> NewsEvent
{
/* to add subscribed method to the event */
.addon instance void Ch08.NewsManager::add_NewsEvent
(class [mscorlib]System.EventHandler'1<class Ch08.NewsEventArgs>)

/* removed unsubscribed method from the event */
.removeon instance void Ch08.NewsManager::remove_NewsEvent
(class [mscorlib]System.EventHandler'1<class Ch08.NewsEventArgs>)
}

/* Execute the subscribed method to send the notification of
* the event from the NewsEvent */
.method family hidebysig newslot virtual instance void OnNewsArrival
(class Ch08.NewsEventArgs args) cil managed
{
.maxstack 3
.locals init (
[0] class [mscorlib]System.EventHandler'1<class Ch08.NewsEventArgs>
newsHandler,
[1] bool CS$4$0000)
L_0000: nop
L_0001: ldarg.0

/* Load the NewsEvent field on the evaluation stack */
L_0002: ldfld class [mscorlib]System.EventHandler'1<class
Ch08.NewsEventArgs> Ch08.NewsManager::NewsEvent

/* and store into the local variable section at position 0. */
L_0007: stloc.0

/* Load the NewsEvent object stored at local variable section
* at position 0.*/
L_0011: ldloc.0

/* Load the object passed as this to this method call*/
L_0012: ldarg.0

/* Load NewsEventArgs object passed as args*/
L_0013: ldarg.1

/* Call the Invoke method from the object loaded in L_0011 to
* start handling the event.*/
L_0014: callvirt instance void [mscorlib]System.EventHandler'1<class
Ch08.NewsEventArgs>::Invoke(object, !0)

L_0019: nop
L_001a: nop
L_001b: ret
}

.method public hidebysig instance void PublishNews (string name, string
detail) cil managed
{
/* Call the OnNewsArrival method on any news send to the
* NewsManager to publish. It calls the OnNewsArrival due to
* send notification to the subscribers */
L_001b: callvirt instance void Ch08.NewsManager::OnNewsArrival
(class Ch08.NewsEventArgs)
L_0020: nop
L_0021: ret
}

/* The event in where the subscriber add the method which will
* be execute later on to send the notification of the event.*/
.field private class [mscorlib]System.EventHandler'1<class
Ch08.NewsEventArgs> NewsEvent

}

Let’s explore the add_NewsEvent and remove_NewsEvent methods, presented in Listing 8-10, to see how the C# compiler generates these methods to add and removed callback method to and from the subscribers to the event.

Listing 8-10. add_NewsEvent and remove_NewsEvent Methods

public void add_NewsEvent(EventHandler<NewsEventArgs> value)
{
EventHandler<NewsEventArgs> handler2;
EventHandler<NewsEventArgs> newsEvent = this.NewsEvent;
do
{
handler2 = newsEvent;
EventHandler<NewsEventArgs> handler3 =
(EventHandler<NewsEventArgs>) Delegate.Combine(handler2, value);
newsEvent = Interlocked.CompareExchange<EventHandler<NewsEventArgs>>
(ref this.NewsEvent, handler3, handler2);
}
while (newsEvent != handler2);
}


public void remove_NewsEvent(EventHandler<NewsEventArgs> value)
{
EventHandler<NewsEventArgs> handler2;
EventHandler<NewsEventArgs> newsEvent = this.NewsEvent;
do
{
handler2 = newsEvent;
EventHandler<NewsEventArgs> handler3 =
(EventHandler<NewsEventArgs>) Delegate.Remove(handler2, value);
newsEvent = Interlocked.CompareExchange<EventHandler<NewsEventArgs>>
(ref this.NewsEvent, handler3, handler2);
}
while (newsEvent != handler2);
}

The add_NewsEvent method is used by the subscribers to subscribe to the event and the remove_NewsEvent method is used to remove the subscription from the event. The OnNewsArrival method from Listing 8-9 is another important method the C# compiler uses for the Invoke method from the NewsManagerclass to trigger the handler to handle the raised event in the NewsManager class.

The C# compiler regenerates the code for the subscribers class, for example, the Publisher and Reviewer classes, to add necessary code to add and remove callback method(s) to and from the event for subscription and un-subscription. As you saw in the C# implementation of the Publisher andReviewer classes, it uses += and -= operators to subscribe and unsubscribe to the event, which will be translated to use the add_NewsEvent and remove_NewsEvent method generated for the event NewsEvent by the C# compiler.

Let’s look at the decompiled IL code for the Publisher class, shown in Listing 8-11, to see how the Publisher class uses the add_NewsEvent and remove_NewsEvent methods.

Listing 8-11. The Decompiled IL Code of the Publisher Class

.class public auto ansi beforefieldinit Publisher
extends [mscorlib]System.Object
{
.method public hidebysig specialname rtspecialname instance void
.ctor(class Ch08.NewsManager nlm) cil managed
{
/* Code removed */

/* It loads the function pointer for the PublishArrivedNews
* method onto the evaluation stack.*/
L_000a: ldftn instance void Ch08.Publisher::PublishArrivedNews
(object, class Ch08.NewsEventArgs)

/* It instantiates an instance of the EventHandler and use
* in the L_0015 to add into the event to get notification.*/
L_0010: newobj instance void
[mscorlib]System.EventHandler'1<class
Ch08.NewsEventArgs>::.ctor(object, native int)

/* The add_NewsEvent is translation for the += and will be
* used to add the subscribed method (created in L_0010)
* into the event.*/
L_0015: callvirt instance void Ch08.NewsManager::
add_NewsEvent(class [mscorlib]System.EventHandler'1<class
Ch08.NewsEventArgs>)
L_001a: nop
L_001b: nop
L_001c: ret
}

.method private hidebysig instance void
PublishArrivedNews(object sender, class Ch08.NewsEventArgs na)
cil managed
{ /*code removed*/ }

.method public hidebysig instance void
UnSubscribe(class Ch08.NewsManager nlm) cil managed
{
/* Code removed */

/* It loads the function pointer for the PublishArrivedNews
* method onto the evaluation stack.*/

L_0003: ldftn instance void Ch08.Publisher::PublishArrivedNews
(object, class Ch08.NewsEventArgs)

/* It instantiates an instance of the EventHandler and use
* in the L_0015 to add into the event to get notification.*/
L_0009: newobj instance void
[mscorlib]System.EventHandler'1<class
Ch08.NewsEventArgs>::.ctor(object, native int)

/* The add_NewsEvent is translation for the += and will be
* used to add the subscribed method
* (created in L_0010) into the event.*/
L_000e: callvirt instance void Ch08.NewsManager::
remove_NewsEvent(class [mscorlib]
System.EventHandler'1<class Ch08.NewsEventArgs>)
L_0013: nop
L_0014: ret
}
}

In Listing 8-11, you can see that in the constructor (.ctor) of the Publisher class, the add_NewsEvent method is used in L_0015 to add the instance of the EventHandler<NewsEventArgs> instantiated in L_0010 using the function pointer of the PublishArrivedNews shown in L_000a. If you examine the UnSubscribe method shown in Listing 8-11, you will see that it loads the function pointer of the PublishArrivedNews shown in L_0003, instantiates an instance of EventHandler<NewsEventArgs> in L_0009, and uses this to call the remove_NewsEvent method in L_000e to unsubscribe the PublishArrivedNews method from the event. The C# compiler compiles the Reviewer class the same the Publisher class does.

In Runtime

While the subscriber, for example, Publisher or Reviewer classes, tries to subscribe for the notification, it calls the add_NewsEvent method from the NewsEvent, which internally calls the Combine method from the Delegate class, which internally called the CombineImpl method from the MulticastDelegate class, as shown in Listing 8-12.

Listing 8-12. Implementation of the Combine Method

public static Delegate Combine(Delegate a, Delegate b)
{
if (a == null)
{
return b;
}
return a.CombineImpl(b);
}

As we saw earlier, the NewsEvent is derived from the EventHandler class, and it is derived from the Delegate class. Therefore, when subscribers call the add_NewsEvent from the NewsEvent, it passes the instance of the EventHandler instantiated using the callback method, for example, PublishArrivedNews for thePublisher and ReviewOnArrivedNews for the Reviewer class, as arguments to the add_NewsEvent method. The CombineImpl method will combine the original NewsEvent with the EventHandler instance and pass it to the add_NewsEvent method. It returns the combined Delegate as MulticastDelegate using the NewMulticastDelegatemethod, as shown in Listing 8-13, and stores it back in the NewsEvent field of the NewsManager.

Listing 8-13. Implementation of the NewMulticastDelegate Method

private MulticastDelegate NewMulticastDelegate(
object[] invocationList, int invocationCount,
bool thisIsMultiCastAlready)
{
MulticastDelegate delegate2 = Delegate.InternalAllocLike(this);
if (thisIsMultiCastAlready)
{
delegate2._methodPtr = base._methodPtr;
delegate2._methodPtrAux = base._methodPtrAux;
}
else
{
delegate2._methodPtr = base.GetMulticastInvoke();
delegate2._methodPtrAux = base.GetInvokeMethod();
}
delegate2._target = delegate2;
delegate2._invocationList = invocationList;
delegate2._invocationCount = (IntPtr) invocationCount;
return delegate2;
}

Figure 8-3 shows that when the CLR tries to subscribe a method in Step A1, it calls the add_NewsEvent from the event by passing an instance of EventHandler instantiated using the PublishArrivedNews or ReviewOnArrivedNews method. In Step A2, the CLR calls the Combine method from the Delegate class to combine this instance of the EventHandler with the NewsEvent field declared in the NewsManager class. In Step A3, the Delegate class returns the combined delegate and it is stored back in the NewsEvent field.

images

Figure 8-3. The event in runtime while adding and removing subscription and executing the event.

While the subscribers tries to unsubscribe (-=) in Step R1, for the notification, for example, from the Publisher class, it calls the remove_NewsEvent method of the NewsEvent event, which calls the Remove method from the Delegate class in Step R2, which internally calls the RemoveImpl method from theMulticastDelegate class to update the NewsEvent, as shown in Listing 8-14.

Listing 8-14. Implementation of the Remove Method from the Delegate Class

public static Delegate Remove(Delegate source, Delegate value)
{
/* Code removed*/
return source.RemoveImpl(value);
}

The CLR passes back the updated Delegate from the RemoveImpl method as MulticastDelegate using the NewMulticastDelegate method in Step R3, and it is stored back in the NewsEvent field in Step R4, as demonstrated in Figure 8-3.

While the CLR executes the OnNewsArrival method, it loads the NewsEvent and calls the Invoke method from the EventHandler. The Invoke method will execute all the methods stored in the _invocationList array. The _invocationList maintains the method pointers of all methods subscribed to the event, as shown in Figure 8-4: method pointer of ReviewOnArrivedNews method 1492808 and method pointer of the PublishArrivedNews method 1492848.

images

Figure 8-4. The memory information while executing the event.

Images The addresses shown in the Figure 8-4 will vary when you execute Listing 8-8 in your environment.

Summary

In this chapter, we have learned about the event, how to design a type that exposes the event to the subscribers to subscribe to be notified, implemented the code to handle the event, and explained the concept of the event in C#. We also explored the behind the scenes workings of the event by looking into the decompiled IL code for the respective event member, event handler, and subscribers code. We also learn how the event works in runtime.

This chapter wraps up by examining a simple Windows application, shown in Listing 8-15, to help you understand how event works in the Windows application. This application will generate a Guid when you click the Generate button. The Button class exposed the Click event, and if you subscribe to this event by providing an event handler and then clicking the instance of the Button, it will execute the subscribed event handler. Listing 8-15 shows the partial code for the event in this Windows application.

Listing 8-15. Sample Windows Application

using System;
using System.Windows.Forms;
namespace Ch08_GUI
{
public partial class frmMain : Form
{
public frmMain()
{
InitializeComponent();
}
/* This method used to subscribe to the Click event of the
* Button class */
private void btnGenerate_Click(object sender, EventArgs e)
{
lblGuid.Text = Guid.NewGuid().ToString("N");
}
}
}

namespace Ch08_GUI
{
partial class frmMain
{
/* code removed */
private void InitializeComponent()
{
this.btnGenerate = new System.Windows.Forms.Button();
/* code removed */
/* Subscribe to the Click event of the Button class using
* the btnGenerate_Click method*/
this.btnGenerate.Click += new
System.EventHandler(this.btnGenerate_Click);
/* code removed */
}
private System.Windows.Forms.Button btnGenerate;
private System.Windows.Forms.Label lblGuid;
}
}

Listing 8-15 produces the output shown in Figure 8-5.

images

Figure 8-5. Sample windows application to show the usage of the event.

The next chapter will examine Iterator in .NET using C#.