[C# Design Pattern] Publish/Subscribe

In this article, I’ll demonstrate how to use the Publish/Subscribe design pattern.

 

Video version here:

 

Where to use it

In many programs, one object needs to know when something happens to another object.

One way to do this is to have the first object check the second object – usually by getting a value from one of the second object’s properties. However, there are two problems with this technique.

First, the first object needs to check the second object every time it does something that affects the second object. This leads to a lot of duplicated code – which is easy to forget to do every time.

Second, a third object might change the second object – and the first object would not know about the change.

A way to avoid these problems is to use the publish/subscribe pattern.

The first object can “subscribe” to an event on the second object. When the second object has a value change, it can “publish” an event to any objects that are subscribed.

This way, the first object will always know about the event – no matter what object, or function, causes the event to happen.

 

For this demonstration, we’ll use classes from a role-playing game. A GameSession object, which manages how the user input works with the other game objects (like a ViewModel, in MVVM). It needs to watch the Player object, to see if the player’s hit points are ever zero, or lower.

 

 

Non-Pattern Version

The GameSession class has two functions that subtract hit points from the player. The first, when a monster attacks the player. The second, if the player moves to a location with a poisonous atmosphere.

Without using the publish/subscribe design pattern, each of those functions needs to check if the player has zero hit points. If we add more functions that subtract hit points from the player, we would need to add this same check in each function.

Location.cs

 

Player.cs

 

GameSession.cs

 

 

Pattern Version

With the publish/subscribe design pattern, we add an “event” to the Player class. Other objects can “subscribe” to this event.

If the player’s hit points ever reach zero, the player object “publishes” a message to any objects subscribed to the PlayerKilled EventHandler.

The GameSession object only needs to subscribe to this EventHandler once. If the player ever reaches zero hit points, from any part of the program, the player object will publish the message.

If we add new functions, where the player will receive damage, we don’t need to have those functions check for zero hit points. The GameSession object will automatically be notified from the Player object.

This makes it much easier to add new game features and capabilities.

Location.cs

 

Player.cs

 

GameSession.cs

 

Pattern Version (with “event”)

When you define the PlayerKilled EventHandler, you can also declare it as an event. This is a more common way to do it.

In the Player.cs class, when you change this line:

to this:

IntelliSense will recognize that the PlayerKilled EventHandler is an event. So, it will show a lightning bolt when you hover over it, like this:

 

 

 

instead of showing it only as a field, like this:

 

 

 

Pattern Version (with custom EventArgs)

You might want to include additional information with your events. To do that, you can create a custom EventArgs class.

Your custom EventArgs class must inherit from the base EventArgs class. Then, you can add whatever properties you want, to hold the additional information.

In this example, the PlayerKilledEventArgs class has a constructor that takes the number of deaths and populates a property with that value.

In the Player class (the publisher), we change the EventHandler to include the type of EventArgs object it will pass – our new PlayerKilledEventArgs object. Then, in the OnPlayerKilled function, we create a new instance of the PlayerKilledEventArgs object, passing in the number of deaths parameter.

In the GameSession class (the subscriber), we change the HandlePlayerKilled function to accept the PlayerKilledEventArgs object. Then, we can add a new line that displays the number of deaths in the list of messages.

 

Location.cs

 

PlayerKilledEventArgs.cs

 

Player.cs

 

GameSession.cs

 

 What to watch out for

You should be aware of possible problems with multi-threaded programs.

If you are using C# 6.0, the code in OnPlayerKilled (in the Pattern Version – with custom EventArgs) is thread-safe. So, if an object subscribes to the event from another thread, and later un-subscribes from it, the program will not throw an exception when the OnPlayerKilled function tries to call Invoke on the PlayerKilled EventHandler.

If you want more details, please see this post on Jon Skeet’s site. That post also shows how to raise events in a thread-safe way, in previous versions of C#.

 

 

Source Code

The source code from these design patterns demonstrations is available at: http://scottlilly.com/GHCSharpDesignPatterns