Understanding C# Events: What They Are and Where They Came From

To get achieve a working knowledge of C# events and event handling we need to understand where events came from, what they really are at a functional level and how they are expressed in C#. Specifically, we have to look at the Gang of Four (GOF) Observer pattern in order to understand and handle events (no pun intended).

Some Groundwork

When we talk about any event model we need at least two things: an observer and the subject they are looking at. Think of it this way:  When we go to a see a movie then we are the observer and the movie is the subject. Easy enough, right?

The subject is going to do something that the observer is interested in. So to continue with our analogy, the movie we have gone to see will start, it will have an intermission, and it will end. These are three important events in the context of our experience with the movie that we want to be aware of.

The subject is responsible for notifying the observer of its events. When the movie starts we are notified by dimming lights. When the intermission starts we are notified by the lights coming back up and the smell of fresh popcorn. When the movie ends we are notified by seeing the credits rolling down the screen.

Because the subject is responsible for notifying the observer of its events it must keep track of who the observer is.  The movie must know how to get our attention and how to broadcast the events we are interested in (usually by dimming the lights). Then we can choose whether or not we want to look out for the event on the chance that we may want to do something about it so we don't miss the beginning of the movie or so we can get some of that great-smelling butter drenched and salted popcorn during intermission.

The Grand-Daddy of Events: Understanding The Observer Pattern

Ok, so now we can get to where events came from. Functionally, C# events are nothing more than a grown up version of the GOF Observer pattern so we'll take a look at that first. One you understand the Observer pattern, understanding events will become easy. There are two interface "contracts" required for the Observer pattern.  You guessed it... ISubject and IObserver. Here are the contracts that our subjects and observers have to adhere to in order to make the magic at the movies work:

interface ISubject

{

          void AddObserver(IObserver observer);

          void NotifyObservers(string message);

}

 

interface IObserver

{

          void Pinged(ISubject subject, string message);

}

Again, our subject (the movie) is responsible for keeping track of and notifying its observers so there are two methods, one to add observers and one to notify them. The subject needs to know how to get our attention and so the IObserver provides a contract that exposes the means to do this. In the above example, it is accomplished through the Pinged() method. When the movie starts, it will "ping" the observers and pass itself.

The Subject

Here's the implementation of the subject (our movie). The subject has an internal array list to keep track of the observers. Observers are added to the list through the AddObserver() method which was implemented from our ISubject interface. Observers are notified through the NotifyObserver() method which iterates through the array list and Pings all the observers.

class Subject: ISubject

{

          public Subject(string name){ m_name = name; }

          private ArrayList m_observers = new ArrayList();

          private string m_name;

          public string Name

          {

                    get{ return m_name; }

                    set { m_name = value; }

          }

 

          #region ISubject Members

 

          public void AddObserver(IObserver observer)

          {

                    m_observers.Add(observer);

                    Observer ob = observer as Observer;

                    Console.WriteLine(ob.Name + " is now observing " + m_name);

          }

 

          public void NotifyObservers(string message)

          {

                    foreach(IObserver obj in m_observers)

                    {

                             obj.Pinged(this, message);

                    }

          }

 

          #endregion

 

}

The Observer

Here's the implementation of the observer (a movie watcher). The move watcher just needs to know when something happens with the subject that it is interested in. When the subject does something interesting, it will "ping" the observer and let it know who's calling and what all the excitement is about. 

class Observer : IObserver

{

          public Observer(string name){ m_name = name; }

          private string m_name;

          public string Name

          {

                    get{ return m_name; }

                    set{ m_name = value; }

          }

 

          #region IObserver Members

 

          public void Pinged(ISubject subject, string message)

          {

                    Subject s = subject as Subject;

                    Console.WriteLine(s.Name + " notified " + m_name + " with message: " + message);

          }

 

          #endregion

 

}

Registering

Now that we have the basic structure in place, we have to wire it all up which should be self-explanatory. Our movie this evening will be BladeRunner (one of my favorites). Frank and Daniel will be attending so they want to know when the movie starts, when there is an intermission and when the movie ends.

class MainProgram

{

          [STAThread]

          static void Main(string[] args)

          {

                    Subject Movie = new Subject("Blade Runner");

 

                    Observer Frank = new Observer("Frank");

                    Movie.AddObserver(Frank);

 

                    Observer Daniel = new Observer("Daniel");

                    Movie.AddObserver(Daniel);

 

                    Movie.NotifyObservers("started");

                    Movie.NotifyObservers("intermission");

                    Movie.NotifyObservers("ended");

          }

}

C# Events

Like we talked about earlier, C# events are nothing more than a implementation of the Observer pattern we worked with earlier in this article with some minor changes. The biggest change is that our ISubject and IObserver interfaces are no longer required because we will be doing all of our notification through a delegate. (If you are not familiar with delegates, I have a brief article on them here).

We will be following the basic structure of our implementation of the Observer pattern we talked about earlier to make it easier to compare the two. First of all we need our delegate that gives us some flexibility on the name and location of the method we will be using to "Ping" the observers.

delegate void NotifyHandler(Subject subject, string message);

 

The Subject

The subject has changed a bit. The event takes place of the array list in our first example. Instead of calling an AddObserver() method, we will be adding a delegate to the event with the += operator. Instead of calling a NotifyObsevers() method, we will be calling FireEvent() and iterating through the delegates in the event and firing each one (we are not required to do this, but this is the best way to see how the C# event model parallels the Observer pattern).

class Subject

{

          public Subject(string name){ m_name = name; }

          private string m_name;

          private event NotifyHandler m_notifyHandler;

 

          #region Events

 

          public event NotifyHandler NotifyObservers

          {

                    add

                    {

                             Console.WriteLine(((Observer) value.Target).Name + " is now observing " + m_name);

                             m_notifyHandler += value;

                    }

                    remove { m_notifyHandler -= value; }

          }

 

          #endregion

 

          #region Event Handling

 

          public void FireEvent(string message)

          {

                    if(null != m_notifyHandler)

                    {

                             foreach(System.Delegate del in m_notifyHandler.GetInvocationList())

                             {

                                       del.DynamicInvoke(new object[] {this, message});

                             }

                    }

          }

          #endregion

}

The Observer

The observer is about the same. Instead of implementing an interface, our observer just has a method with the same signature as the delegate that the subject will be firing off. Usually this method name starts with "On" (like with OnClick, OnLeave, etc).

class Observer
{

          public Observer(string name) { m_name = name; }

 

          private string m_name;

          public string Name

          {

                    get{ return m_name; }

                    set{ m_name = value; }

          }

 

          public void OnPinged(Subject subject, string message)

          {     

                    Console.WriteLine(subject.Name + " notified " + m_name + " with message: " + message);

          }

}

Registering

The wire-up is similar to the observer pattern except there is a special syntax for registering with the event and we are now registering delegates to the OnPinged() method in Frank and Daniel instead of having to implement an interface method.

[STAThread]

static void Main(string[] args)

{

Subject Movie = new Subject("Blade Runner");

Observer Frank = new Observer("Frank");

Movie.NotifyObservers += new NotifyHandler(Frank.OnPinged);

Observer Daniel = new Observer("Daniel");

Movie.NotifyObservers += new NotifyHandler(Daniel.OnPinged);

Movie.FireEvent("started");

Movie.FireEvent("intermission");

Movie.FireEvent("ended");

}

Wrap up

Hopefully this article has helped you gain a better understanding of events in C#. As you can see, the event model is a direct implementation of the Observer pattern. I just wanted to point out a couple more things.

Usually you'll see events fired off with the following syntax which iterates through each of the delegates in the event and fires each one in turn:

public void FireEvent2(string message)

{

if(null != m_notifyHandler)

{

m_notifyHandler(this, message);

}

}

To unregister for an event you can use the -+ syntax.

Movie.NotifyObservers -= new NotifyHandler(Frank.OnPinged);

Until next time,

-Happy coding


Similar Articles