Subscription publishing mode of C design mode

Keywords: C# github

What is pub sub

Publish and subscribe is a design pattern that allows loose coupling between application components.
In fact, in the design of subscription publishing, event channel is mainly generated by the publisher, which is used to notify the subscriber without knowing the existence of any subscriber.

Of course, delegate EventHandlers and Event keywords play an important role in this Event handling mechanism. Let's see how to use them.

Use of Pub and Sub

First, let's look at a simple subscription publishing model

Define an Action delegate with no return value

namespace PubSubPattern
{
    public class Pub
    {
        public Action OnChange { get; set; }

        public void Raise()
        {
            if (OnChange != null)
            {
                //Invoke OnChange Action
                OnChange();
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var p = new Pub();
            p.OnChange += () => Console.WriteLine("Sub 1");

            p.OnChange += () => Console.WriteLine("Sub 2");

            p.Raise();

            Console.WriteLine("Press enter !");
            Console.ReadLine();

        }
    }
}

In the above code, we create a publisher, and we call the delegate to create our anonymous method to subscribe. Because delegates provide multicast, we can use + =. On the OnChange property

Although we see that the above code is executed correctly, there are still some problems in the program. If = is used instead of + =, the first subscriber will be deleted in OnChange property.
Because OnChange is a public property, any external user of the class can call p.OnChange()

Publish subscriptions using the Event keyword

Let's take a look at the code after using the event keyword

    public class Pub
    {
        public event Action OnChange = delegate { };

        public void Raise()
        {
            OnChange();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Pub p = new Pub();
            p.OnChange += () => Console.WriteLine("Sub 1");
            p.OnChange += () => Console.WriteLine("Sub 2");
            p.Raise();
            Console.WriteLine("Press enter !");
            Console.ReadLine();
        }
    }

Through the above code, we try to solve the problem we mentioned in the first place. We will find that using the event keyword can protect our OnChange from unnecessary access. It doesn't allow = that is, it doesn't allow direct delegation, so we can now avoid using =, thus avoiding unnecessary application trouble.

You may also find that OnChange is initialized to a null delegate {}. This ensures that our OnChange will never be empty. Because when we make other calls to him, we can delete the non empty check to him in the code

Publish subscriptions using EventHandlers

In fact, in subscription publishing, neither the publisher nor the subscriber knows each other's existence. There is an EventHandler, which is called message broker or event bus. Both publisher and subscriber should know it. It receives all incoming messages and forwards them

So, in the following snippet, we use EventHandler instead of Action

public delegate void EventHandler(
    object sender,
    EventArgs e
)

By default, the EventHandler takes the send object and some event parameters as parameters.

  public class MyEventArgs : EventArgs
        {
            public int Value { get; set; }

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

        public class Pub
        {
            public event EventHandler<MyEventArgs> OnChange = delegate { };
            public void Raise()
            {
                OnChange(this, new MyEventArgs(1));
            }
        }
        class Program
        {
            static void Main(string[] args)
            {
                Pub p = new Pub();
                p.OnChange += (sender, e) => Console.WriteLine("Sub 1.Value:" + e.Value);
                p.OnChange += (sender, e) => Console.WriteLine("Sub 2.Value:" + e.Value);
                p.Raise();
                Console.WriteLine("Press enter !");
                Console.ReadLine();
            }
        }

In the above code, the general EventHandler is used through the pub class, which is the event parameter type to be passed when triggering the EventHandler OnChange. In the above code snippet, it is MyArgs

Exception in event

Let's continue with a situation. Let's see the following code snippet

    public class MyEventArgs : EventArgs
    {
        public int Value { get; set; }

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

    public class Pub
    {
        public event EventHandler<MyEventArgs> OnChange = delegate { };
        public void Raise()
        {
            OnChange(this, new MyEventArgs(1));
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Pub p = new Pub();
            p.OnChange += (sender, e) => Console.WriteLine("Sub 1.Value:" + e.Value);
            p.OnChange += (sender, e) => { throw new Exception(); };
            p.OnChange += (sender, e) => Console.WriteLine("Sub 2.Value:" + e.Value);
            p.Raise();
            Console.WriteLine("Press enter !");
            Console.ReadLine();
        }
    }

After running the above code, you will find that the first subscriber has executed successfully, the second subscriber has caused an exception, and the third subscriber has not been called. This is a very embarrassing thing

If we think the above process is not what we expected, we need to manually raise events and handle exceptions, then we can use getinvocationlist defined in the Delegate base class to help us achieve these.

Let's continue to look at the following code

public class MyEventArgs : EventArgs
        {
            public int Value { get; set; }

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

        public class Pub
        {

            public event EventHandler<MyEventArgs> OnChange = delegate { };

            public void Raise()
            {
                MyEventArgs eventArgs = new MyEventArgs(1);

                List<Exception> exceptions = new List<Exception>();

                foreach (Delegate handler in OnChange.GetInvocationList())
                {
                    try
                    {
                        handler.DynamicInvoke(this, eventArgs);
                    }
                    catch (Exception e)
                    {
                        exceptions.Add(e);
                    }
                }

                if (exceptions.Any())
                {
                    throw new AggregateException(exceptions);
                }
            }
        }
        class Program
        {
            static void Main(string[] args)
            {
                Pub p = new Pub();
                p.OnChange += (sender, e) => Console.WriteLine("Sub 1.Value:" + e.Value);
                p.OnChange += (sender, e) => { throw new Exception(); };
                p.OnChange += (sender, e) => Console.WriteLine("Sub 2.Value:" + e.Value);
                p.Raise();
                Console.WriteLine("Press enter !");
                Console.ReadLine();
            }
        }

Reference

https://github.com/hueifeng/DesignPatterns-Samples/tree/master/PubSubPattern

https://hackernoon.com/observer-vs-pub-sub-pattern-50d3b27f838c

Posted by svguerin3 on Thu, 21 May 2020 06:57:46 -0700