Publish Subscribe Design Pattern In C#

Contents

What is Pub-Sub?

Publish-Subscribe or Pub-Sub is a design pattern that allows loose coupling between the application components.

Here senders of messages, called publishers, do not program the messages to be sent directly to specific receivers, called subscribers. Messages are published without the knowledge of what or if any subscriber of that knowledge exists. Delegate form the basis of this design pattern in C#.

There is also another design pattern which is much similar to pub-sub that is the Observer pattern.

However,

The major difference in Pub-Sub design is the event channel that publisher generates to notify changes to its subscriber without any knowledge of subscriber existence.

Delegates, EventHandlers and Event keyword plays a special role in this event handling mechanism. Let’s see how they are used in Pub-Sub.

Note: You can learn more about C# delegates in my blog post Delegates And Events In C#.

Pub-Sub Using Delegate

First, let’s see the implementation of Pub-Sub pattern in C# using Action delegate defined in .NET Framework.

An Action delegate is nothing but a built-in delegate with return type as void.

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 
 7 //Define publisher class as Pub
 8 public class Pub
 9 {
10     //OnChange property containing all the 
11     //list of subscribers callback methods
12     public Action OnChange { get; set; }
13 
14     public void Raise()
15     {
16         //Check if OnChange Action is set before invoking it
17         if(OnChange!=null)
18         {
19             //Invoke OnChange Action
20             OnChange();
21         }
22     }
23 }
24 
25 class Program
26 {
27     static void Main(string[] args)
28     {
29         //Initialize pub class object
30         Pub p = new Pub();
31 
32         //register for OnChange event - Subscriber 1
33         p.OnChange += () => Console.WriteLine("Subscriber 1!");
34         //register for OnChange event - Subscriber 2
35         p.OnChange += () => Console.WriteLine("Subscriber 2!");
36 
37         //raise the event
38         p.Raise();
39 
40         //After this Raise() method is called
41         //all subscribers callback methods will get invoked
42 
43         Console.WriteLine("Press enter to terminate!");
44         Console.ReadLine();
45     }
46 }

In above code, we created a publisher and subscribe to its events using two anonymous methods sharing the signature of Action delegate.

We able to use += on the OnChange property because of the multicast feature provided by delegates.

Note: You can learn more about multicast feature in delegates in my blog post Multicast Delegates In C#.

Although the programs look good there is some weakness in the program.

If you use = instead of += you will effectively remove the first subscriber from the OnChange property.

Also,

As OnChange is a public property any outside user of the class can raise the event by just calling p.OnChange().

Pub-Sub Using Event Keyword

Let’s see the same code using event keyword,

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 
 7 //Define publisher class as Pub
 8 public class Pub
 9 {
10     //OnChange property containing all the 
11     //list of subscribers callback methods
12     public event Action OnChange = delegate { };
13 
14     public void Raise()
15     {            
16         //Invoke OnChange Action
17         OnChange();
18     }
19 }
20 
21 class Program
22 {
23     static void Main(string[] args)
24     {
25         //Initialize pub class object
26         Pub p = new Pub();
27 
28         //register for OnChange event - Subscriber 1
29         p.OnChange += () => Console.WriteLine("Subscriber 1!");
30         //register for OnChange event - Subscriber 2
31         p.OnChange += () => Console.WriteLine("Subscriber 2!");
32 
33         //raise the event
34         p.Raise();
35 
36         //After this Raise() method is called
37         //all subscribers callback methods will get invoked
38 
39         Console.WriteLine("Press enter to terminate!");
40         Console.ReadLine();
41     }
42 }

As you can see in above code, with the use of event keyword we are using OnChange as a field instead of property. I know its still public.

But,

By using event keyword compiler protects our field from unwanted access.

And,

It does not permit the use of = (direct assignment of a delegate). Hence, your code is now safe from the risk of removing previous subscribers by using = instead of +=.

Also, you may have noticed special syntax of initializing the OnChange field to an empty delegate like this delegate { }. This ensures that our OnChange field will never be null. Hence, we can remove the null check before raising the event, if no other members of the class making it null.

Pub-Sub Using EventHandlers

In Pub-Sub, the publisher and subscriber don’t know about the existence of one another. There is a third component, called broker or message broker or event bus, which is known by both the publisher and subscriber, which filters all incoming messages and distributes them accordingly.

Here EventHandler is our that third component that acts as event bus between publisher and subscriber.

So instead of using Action delegate, we have to use EventHandler which is defined as the following delegate:

1 public delegate void EventHandler(
2     object sender,
3     EventArgs e
4 )

By default, EventHandler takes sender object and some event argument as a parameter. Here sender is the same object which raises the event, which is publisher in our case.

Let’s see an example

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 
 7 //Define event argument you want to send while raising event.
 8 public class MyEventArgs:EventArgs
 9 {
10     public int Value { get; set; }
11 
12     public MyEventArgs(int value)
13     {
14         Value = value;
15     }
16 }
17 
18 
19 //Define publisher class as Pub
20 public class Pub
21 {
22     //OnChange property containing all the 
23     //list of subscribers callback methods.
24 
25     //This is generic EventHandler delegate where 
26     //we define the type of argument want to send 
27     //while raising our event, MyEventArgs in our case.
28     public event EventHandler<MyEventArgs> OnChange = delegate { };
29 
30     public void Raise()
31     {
32         //Invoke OnChange Action
33         //Lets pass MyEventArgs object with some random value
34         OnChange(this,new MyEventArgs(33));
35     }
36 }
37 
38 class Program
39 {
40     static void Main(string[] args)
41     {
42         //Initialize pub class object
43         Pub p = new Pub();
44 
45         //register for OnChange event - Subscriber 1
46         p.OnChange += (sender,e) => Console.WriteLine("Subscriber 1! Value:" + e.Value);
47         //register for OnChange event - Subscriber 2
48         p.OnChange += (sender,e) => Console.WriteLine("Subscriber 2! Value:" + e.Value);
49 
50         //raise the event
51         p.Raise();
52 
53         //After this Raise() method is called
54         //all subscribers callback methods will get invoked
55 
56         Console.WriteLine("Press enter to terminate!");
57         Console.ReadLine();
58     }
59 }

Here the pub class uses generic EventHandler, which specifies the type of event argument you need to pass to the while raising the EventHandler OnChange, MyArgs in our case

Exception Aware Event Raise

Let’s start with code this time

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 
 7 //Define event argument you want to send while raising event.
 8 public class MyEventArgs : EventArgs
 9 {
10     public int Value { get; set; }
11 
12     public MyEventArgs(int value)
13     {
14         Value = value;
15     }
16 }
17 
18 
19 //Define publisher class as Pub
20 public class Pub
21 {
22     //OnChange property containing all the 
23     //list of subscribers callback methods.
24 
25     //This is generic EventHandler delegate where 
26     //we define the type of argument want to send 
27     //while raising our event, MyEventArgs in our case.
28     public event EventHandler<MyEventArgs> OnChange = delegate { };
29 
30     public void Raise()
31     {
32         //Invoke OnChange Action
33         //Lets pass MyEventArgs object with some random value
34         OnChange(this, new MyEventArgs(33));
35     }
36 }
37 
38 class Program
39 {
40     static void Main(string[] args)
41     {
42         //Initialize pub class object
43         Pub p = new Pub();
44 
45         //register for OnChange event - Subscriber 1
46         p.OnChange += (sender, e) => Console.WriteLine("Subscriber 1! Value:" + e.Value);
47         //register for OnChange event - Subscriber 2
48         p.OnChange += (sender, e) => { throw new Exception(); };
49         //register for OnChange event - Subscriber 3
50         p.OnChange += (sender, e) => Console.WriteLine("Subscriber 3! Value:" + e.Value);
51 
52         //raise the event
53         p.Raise();
54 
55         //After this Raise() method is called
56         //all subscribers callback methods will get invoked
57 
58         Console.WriteLine("Press enter to terminate!");
59         Console.ReadLine();
60     }
61 }

When you run this code you will find that the first subscriber is executed successfully and the second one throws an exception and the third one is never called.

If this not the behavior you want, you need to manually raise the event and handle the exception. This can be achieved by using the GetInvoctionList method defined in Delegate base class.

Let’s see the code example

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 
 7 //Define event argument you want to send while raising event.
 8 public class MyEventArgs : EventArgs
 9 {
10     public int Value { get; set; }
11 
12     public MyEventArgs(int value)
13     {
14         Value = value;
15     }
16 }
17 
18 
19 //Define publisher class as Pub
20 public class Pub
21 {
22     //OnChange property containing all the 
23     //list of subscribers callback methods.
24 
25     //This is generic EventHandler delegate where 
26     //we define the type of argument want to send 
27     //while raising our event, MyEventArgs in our case.
28     public event EventHandler<MyEventArgs> OnChange = delegate { };
29 
30     public void Raise()
31     {
32         //Initialize MyEventArgs object with some random value
33         MyEventArgs eventArgs = new MyEventArgs(33);
34 
35         //Create list of exception
36         List<Exception> exceptions = new List<Exception>();
37 
38         //Invoke OnChange Action by iterating on all subscribers event handlers
39         foreach (Delegate handler in OnChange.GetInvocationList())
40         {
41             try
42             {
43                 //pass sender object and eventArgs while
44                 handler.DynamicInvoke(this, eventArgs);
45             }
46             catch (Exception e)
47             {
48                 //Add exception in exception list if occured any
49                 exceptions.Add(e);
50             }
51         }
52 
53         //Check if any exception occured while 
54         //invoking the subscribers event handlers
55         if(exceptions.Any())
56         {
57             //Throw aggregate exception of all exceptions 
58             //occured while invoking subscribers event handlers
59             throw new AggregateException(exceptions);
60         }
61     }
62 }
63 
64 class Program
65 {
66     static void Main(string[] args)
67     {
68         //Initialize pub class object
69         Pub p = new Pub();
70 
71         //register for OnChange event - Subscriber 1
72         p.OnChange += (sender, e) => Console.WriteLine("Subscriber 1! Value:" + e.Value );
73         //register for OnChange event - Subscriber 2
74         p.OnChange += (sender, e) => { throw new Exception(); };
75         //register for OnChange event - Subscriber 3
76         p.OnChange += (sender, e) => Console.WriteLine("Subscriber 3! Value:" + e.Value );
77 
78         //raise the event
79         p.Raise();
80 
81         //After this Raise() method is called
82         //all subscribers callback methods will get invoked
83 
84         Console.WriteLine("Press enter to terminate!");
85         Console.ReadLine();
86     }
87 }

Where To Apply Publish Subscribe Pattern?

  • When you want loose coupling between objects that interact with each other.

  • When you want to specify the kind of notification you want to send, by defining a number of events, to a subscriber depending on the type or scope of change. The subscriber will thus be able to choose to subscribe a specific notifications that matters.

Note: You can download the complete solution demo from my github repository.

Further Reading

  • Observer Pattern C# - The observer design pattern enables a subscriber to register with and receive notifications from a provider. It is suitable for any scenario that requires push-based notification. The pattern defines a provider (also known as a subject or an observable) and zero, one, or more observers.

  • Observer vs Pub-Sub pattern by Ahmed Shamim Hassan - Despite the differences between these patterns, some might say that Publisher-Subscriber pattern is a variation of Observer pattern because of the conceptual similarity between them. However, Ahmed Shamim explains how they are different from each other in a very illustrative way!

Publish Subscribe Design Pattern In C#
Share this

Subscribe to Code with Shadman