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 }

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