Shadman Kudchikar

Publish Subscribe Design Pattern In C#

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 Observable 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.

You can learn more about the difference between Pub-Sub and Observable design pattern here.

Delegates, EventHandlers and Event keyword plays a special role in this event handling mechanism

You can learn more about C# delegates here.

Let’s see how they are used in Pub-Sub.

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.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _01_Pub_Sub_Using_Delegate
{
    
    //Define publisher class as Pub
    public class Pub
    {
        //OnChange property containing all the 
        //list of subscribers callback methods
        public Action OnChange { get; set; }

        public void Raise()
        {
            //Check if OnChange Action is set before invoking it
            if(OnChange!=null)
            {
                //Invoke OnChange Action
                OnChange();
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            //Initialize pub class object
            Pub p = new Pub();

            //register for OnChange event - Subscriber 1
            p.OnChange += () => Console.WriteLine("Subscriber 1!");
            //register for OnChange event - Subscriber 2
            p.OnChange += () => Console.WriteLine("Subscriber 2!");

            //raise the event
            p.Raise();

            //After this Raise() method is called
            //all subscribers callback methods will get invoked

            Console.WriteLine("Press enter to terminate!");
            Console.ReadLine();
        }
    }
}

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 be delegates.

You can learn more about multicast feature in delegates here.

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,

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _02_Event_Keyword
{
    //Define publisher class as Pub
    public class Pub
    {
        //OnChange property containing all the 
        //list of subscribers callback methods
        public event Action OnChange = delegate { };

        public void Raise()
        {            
            //Invoke OnChange Action
            OnChange();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            //Initialize pub class object
            Pub p = new Pub();

            //register for OnChange event - Subscriber 1
            p.OnChange += () => Console.WriteLine("Subscriber 1!");
            //register for OnChange event - Subscriber 2
            p.OnChange += () => Console.WriteLine("Subscriber 2!");

            //raise the event
            p.Raise();

            //After this Raise() method is called
            //all subscribers callback methods will get invoked

            Console.WriteLine("Press enter to terminate!");
            Console.ReadLine();
        }
    }
}

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:

public delegate void EventHandler(
    object sender,
    EventArgs e
)

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _03_EventHandler
{
    //Define event argument you want to send while raising event.
    public class MyEventArgs:EventArgs
    {
        public int Value { get; set; }

        public MyEventArgs(int value)
        {
            Value = value;
        }
    }


    //Define publisher class as Pub
    public class Pub
    {
        //OnChange property containing all the 
        //list of subscribers callback methods.

        //This is generic EventHandler delegate where 
        //we define the type of argument want to send 
        //while raising our event, MyEventArgs in our case.
        public event EventHandler<MyEventArgs> OnChange = delegate { };

        public void Raise()
        {
            //Invoke OnChange Action
            //Lets pass MyEventArgs object with some random value
            OnChange(this,new MyEventArgs(33));
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            //Initialize pub class object
            Pub p = new Pub();

            //register for OnChange event - Subscriber 1
            p.OnChange += (sender,e) => Console.WriteLine("Subscriber 1! Value:" + e.Value);
            //register for OnChange event - Subscriber 2
            p.OnChange += (sender,e) => Console.WriteLine("Subscriber 2! Value:" + e.Value);

            //raise the event
            p.Raise();

            //After this Raise() method is called
            //all subscribers callback methods will get invoked

            Console.WriteLine("Press enter to terminate!");
            Console.ReadLine();
        }
    }
}

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _04_Exception_While_Raising_Event
{
    //Define event argument you want to send while raising event.
    public class MyEventArgs : EventArgs
    {
        public int Value { get; set; }

        public MyEventArgs(int value)
        {
            Value = value;
        }
    }


    //Define publisher class as Pub
    public class Pub
    {
        //OnChange property containing all the 
        //list of subscribers callback methods.

        //This is generic EventHandler delegate where 
        //we define the type of argument want to send 
        //while raising our event, MyEventArgs in our case.
        public event EventHandler<MyEventArgs> OnChange = delegate { };

        public void Raise()
        {
            //Invoke OnChange Action
            //Lets pass MyEventArgs object with some random value
            OnChange(this, new MyEventArgs(33));
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            //Initialize pub class object
            Pub p = new Pub();

            //register for OnChange event - Subscriber 1
            p.OnChange += (sender, e) => Console.WriteLine("Subscriber 1! Value:" + e.Value);
            //register for OnChange event - Subscriber 2
            p.OnChange += (sender, e) => { throw new Exception(); };
            //register for OnChange event - Subscriber 3
            p.OnChange += (sender, e) => Console.WriteLine("Subscriber 3! Value:" + e.Value);

            //raise the event
            p.Raise();

            //After this Raise() method is called
            //all subscribers callback methods will get invoked

            Console.WriteLine("Press enter to terminate!");
            Console.ReadLine();
        }
    }
}

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _05_Exception_Aware_Event_Raise
{
    //Define event argument you want to send while raising event.
    public class MyEventArgs : EventArgs
    {
        public int Value { get; set; }

        public MyEventArgs(int value)
        {
            Value = value;
        }
    }


    //Define publisher class as Pub
    public class Pub
    {
        //OnChange property containing all the 
        //list of subscribers callback methods.

        //This is generic EventHandler delegate where 
        //we define the type of argument want to send 
        //while raising our event, MyEventArgs in our case.
        public event EventHandler<MyEventArgs> OnChange = delegate { };

        public void Raise()
        {
            //Initialize MyEventArgs object with some random value
            MyEventArgs eventArgs = new MyEventArgs(33);

            //Create list of exception
            List<Exception> exceptions = new List<Exception>();

            //Invoke OnChange Action by iterating on all subscribers event handlers
            foreach (Delegate handler in OnChange.GetInvocationList())
            {
                try
                {
                    //pass sender object and eventArgs while
                    handler.DynamicInvoke(this, eventArgs);
                }
                catch (Exception e)
                {
                    //Add exception in exception list if occured any
                    exceptions.Add(e);
                }
            }

            //Check if any exception occured while 
            //invoking the subscribers event handlers
            if(exceptions.Any())
            {
                //Throw aggregate exception of all exceptions 
                //occured while invoking subscribers event handlers
                throw new AggregateException(exceptions);
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            //Initialize pub class object
            Pub p = new Pub();

            //register for OnChange event - Subscriber 1
            p.OnChange += (sender, e) => Console.WriteLine("Subscriber 1! Value:" + e.Value );
            //register for OnChange event - Subscriber 2
            p.OnChange += (sender, e) => { throw new Exception(); };
            //register for OnChange event - Subscriber 3
            p.OnChange += (sender, e) => Console.WriteLine("Subscriber 3! Value:" + e.Value );

            //raise the event
            p.Raise();

            //After this Raise() method is called
            //all subscribers callback methods will get invoked

            Console.WriteLine("Press enter to terminate!");
            Console.ReadLine();
        }
    }
}

Well, that’s all for now. Hope you get the idea. Thanks for reading the post. Please let me know if there is any mistake or any modification needed in comment below. Thanks in advance!


comments powered by Disqus