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