[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#.

 

 

All my design pattern lessons

Source code for my design pattern lessons

 

8 thoughts on “[C# Design Pattern] Publish/Subscribe

  1. Hello Scott – thank you again for doing the video and helping us learn and understand c# better.

    Since you ask for suggestions for topics – I have a question.

    What in your opinion is the best strategy for Exception logging in a project that involves multiple business objects some of which also connect to a database.

    Would it make sense to handle it centrally by throwing exceptions (while preserving the callstack by using the ExceptionDispatchInfo object or something similar) after handling local operations like disposing unmanaged objects, closing connections etc. and letting the central code log exceptions, OR
    if we handle it onsite what is a good strategy to bubble up the chain, the fact that –
    A. there was an exception down the chain and
    B. the fact that the exception was actually handled and logged.

    Thank you.

    1. You’re welcome.

      Error-handling strategies is a good idea for a future post (or maybe two or three – there is a lot to cover). For now, a couple of quick comments on how I usually handle errors:

      1. I normally let the error bubble up to the top, and let the top level of the program handle the display/logging of the error.
      2. To ensure disposable objects are properly disposed, I use a “using” block (more details here: http://stackoverflow.com/questions/4717789/in-a-using-block-is-a-sqlconnection-closed-on-return-or-exception).

  2. ***Not sure if I replied already, please ignore if I already did!***

    Thanks Scott.

    Yes I’ve wrapped my DB code in using statements, I was referring to closing/disposing connections as an example of local cleanup needed prior to bubbling up the exception for logging.

    In my code I’m currently using the logging strategy that you outlined above – handle clean up locally and then handle logging centrally.

    Please consider doing a video/article on different strategies for exception handling/logging.

    Thank you.

  3. Thank you Scott for the well structured and detailed videos.
    They did not let questions unanswered.
    It wluld be great if you could cover also Repository and UnitOfWork.
    Thanks again!

  4. Hi Scott, can you tell me what difference it will make if I use a Action callback instead of Event. As I use it very often.

    1. Hello Vinod, I have not used Action callbacks with this pattern. It seems like a good idea for event-handling code that needs to perform the same type of function, but can be slightly changed with a parameter. I want to try this in some of my code. Have you seen any problems with using them?

Leave a Reply

Your email address will not be published. Required fields are marked *