In this article, you will learn about the Observer Design Pattern in .NET C# with some enhancements. Definition Observer Design Pattern The is one of the most important and commonly used design patterns. Observer Design Pattern First, let’s check the formal definition of the . Observer Design Pattern As per : Microsoft’s documentation 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. Observers register with the provider, and whenever a predefined condition, event, or state change occurs, the provider automatically notifies all observers by calling one of their methods. In this method call, the provider can also provide current state information to observers. In .NET, the observer design pattern is applied by implementing the generic and interfaces. The generic type parameter represents the type that provides notification information. System.IObservable<T> System.IObserver<T> So, from the definition above, we can understand the following: We have two parties or modules. The module which has some stream of information to provide. This module is called (as it provides information), or (as it subjects information to outside world), or (as it could be observed by outside world). Provider Subject Observable The module which is interested into a stream of information coming from somewhere else. This module is called (as it observes information). Observer Advantages of Observer Design Pattern As we now know, the formulates the relation between the and modules. What makes the unique is that using it you can achieve this without having a tightly coupled relation. Observer Design Pattern Observable Observer Observer Design Pattern Analyzing the way the pattern works, you would find the following: The knows the minimal information needed about the . Observable Observer The knows the minimal information needed about the . Observer Observable Even the mutual knowledge is achieved through abstractions, not concrete implementations. At the end, both modules can do their job, and only their job. Abstractions Used These are the used to implement the in . abstractions Observer Design Pattern .NET C# IObservable<out T> This is a interface representing any . If you want to know more about Variance in .NET, you can check the article . Covariant Observable Covariance and Contravariance in .NET C# Members defined in this interface are: public IDisposable Subscribe (IObserver<out T> observer); The method should be called to inform the that some is interested into its stream of information. Subscribe Observable Observer The method returns an object which implements the interface. This object could then be used by the to unsubscribe from the stream of information provided by the . Once this is done, the would not be notified about any updates to the stream of information. Subscribe IDisposable Observer Observable Observer IObserver<in T> This is a interface representing any . If you want to know more about Variance in .NET, you can check the article . Contravariant Observer Covariance and Contravariance in .NET C# Members defined in this interface are: public void OnCompleted (); public void OnError (Exception error); public void OnNext (T value); The method should be called by the to inform the that the stream of information is completed and the should not expect any more information. OnCompleted Observable Observer Observer The method should be called by the to inform the that an error has occurred. OnError Observable Observer The method should be called by the to inform the that a new piece of info is ready and is being added to the stream. OnNext Observable Observer Microsoft’s Implementation Now, let’s see how recommends implementing the in C#. Later, I will show you some minor enhancements I implemented myself. Microsoft Observer Design Pattern We will build a simple . In this application, we will have module (Observable, Provider, Subject) and module (Observer). Weather Forecast Console Application WeatherForecast WeatherForecastObserver So, let’s begin looking into the implementation. WeatherInfo namespace Observable { public class WeatherInfo { internal WeatherInfo(double temperature) { Temperature = temperature; } public double Temperature { get; } } } This is the entity representing the piece of information to be flowing in the information stream. WeatherForecast using System; using System.Collections.Generic; namespace Observable { public class WeatherForecast : IObservable<WeatherInfo> { private readonly List<IObserver<WeatherInfo>> m_Observers; private readonly List<WeatherInfo> m_WeatherInfoList; public WeatherForecast() { m_Observers = new List<IObserver<WeatherInfo>>(); m_WeatherInfoList = new List<WeatherInfo>(); } public IDisposable Subscribe(IObserver<WeatherInfo> observer) { if (!m_Observers.Contains(observer)) { m_Observers.Add(observer); foreach (var item in m_WeatherInfoList) { observer.OnNext(item); } } return new WeatherForecastUnsubscriber(m_Observers, observer); } public void RegisterWeatherInfo(WeatherInfo weatherInfo) { m_WeatherInfoList.Add(weatherInfo); foreach (var observer in m_Observers) { observer.OnNext(weatherInfo); } } public void ClearWeatherInfo() { m_WeatherInfoList.Clear(); } } } What we can notice here: The class is implementing . WeatherForecast IObservable<WeatherInfo> In the implementation of the method, we check if the passed in Observer was already registered before or not. If not, we add it to the local observers list. Then, we loop on all the entries we have in the local list one by one and inform the Observer about it by calling the method of the Observer. Subscribe m_Observers WeatherInfo m_WeatherInfoList OnNext Finally, we return a new instance of class to be used by the Observer for unsubscribing from the information stream. WeatherForecastUnsubscriber The method is defined so that the main module can register new . In the real world, this could be replaced by an internal scheduled or a listener to a or something else that would act as a source of information. RegisterWeatherInfo WeatherInfo API call SignalR Hub Unsubscriber<T> using System; using System.Collections.Generic; namespace Observable { public class Unsubscriber<T> : IDisposable { private readonly List<IObserver<T>> m_Observers; private readonly IObserver<T> m_Observer; private bool m_IsDisposed; public Unsubscriber(List<IObserver<T>> observers, IObserver<T> observer) { m_Observers = observers; m_Observer = observer; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (m_IsDisposed) return; if (disposing && m_Observers.Contains(m_Observer)) { m_Observers.Remove(m_Observer); } m_IsDisposed = true; } ~Unsubscriber() { Dispose(false); } } } What we can notice here: This is a base class for any Un-subscriber. It implements by applying the . IDisposable Disposable Design Pattern Through the constructor, it takes in the full list of Observers and the Observer it is created for. While disposing, it checks if the Observer already exists into the full list of Observers. If yes, it removes it from the list. WeatherForecastUnsubscriber using System; using System.Collections.Generic; namespace Observable { public class WeatherForecastUnsubscriber : Unsubscriber<WeatherInfo> { public WeatherForecastUnsubscriber( List<IObserver<WeatherInfo>> observers, IObserver<WeatherInfo> observer) : base(observers, observer) { } } } What we can notice here: This is inheriting from class. Unsubscriber<T> No special handling is happening. WeatherForecastObserver using System; namespace Observable { public class WeatherForecastObserver : IObserver<WeatherInfo> { private IDisposable m_Unsubscriber; public virtual void Subscribe(WeatherForecast provider) { m_Unsubscriber = provider.Subscribe(this); } public virtual void Unsubscribe() { m_Unsubscriber.Dispose(); } public void OnCompleted() { Console.WriteLine("Completed"); } public void OnError(Exception error) { Console.WriteLine("Error"); } public void OnNext(WeatherInfo value) { Console.WriteLine($"Temperature: {value.Temperature}"); } } } What we can notice here: The class is implementing . WeatherForecastObserver IObserver<WeatherInfo> On the method, we are writing the temperature to the console. OnNext On the method, we are writing “Completed” to the console. OnCompleted On the method, we are writing “Error” to the console. OnError We defined method to allow the main module to trigger the registration process. The un-subscriber object returned is saved internally to be used in case of unsubscribing. void Subscribe(WeatherForecast provider) Using the same concept, the method is defined and it makes use of the internally saved un-subscriber object. void Unsubscribe() Program using System; namespace Observable { class Program { static void Main(string[] args) { var provider = new WeatherForecast(); provider.RegisterWeatherInfo(new WeatherInfo(1)); provider.RegisterWeatherInfo(new WeatherInfo(2)); provider.RegisterWeatherInfo(new WeatherInfo(3)); var observer = new WeatherForecastObserver(); observer.Subscribe(provider); provider.RegisterWeatherInfo(new WeatherInfo(4)); provider.RegisterWeatherInfo(new WeatherInfo(5)); observer.Unsubscribe(); provider.RegisterWeatherInfo(new WeatherInfo(6)); observer.Subscribe(provider); provider.RegisterWeatherInfo(new WeatherInfo(7)); Console.ReadLine(); } } } What we can notice here: We created an instance of the provider. Then registered 3 pieces of info. Up to this moment, nothing should be logged to the console as no observers are defined. Then created an instance of the observer. Then subscribed the observer to the stream. At this moment, we should find 3 logged temperatures in the console. This is because when the observer subscribes, it gets notified about the already existing information and in our case, they are 3 pieces of information. Then we register 2 pieces of info. So, we get 2 more messages logged to the console. Then we unsubscribe. Then we register 1 piece of info. However, this piece of info would not be logged to the console as the observer had already unsubscribed. Then the observer subscribes again. Then we register 1 piece of info. So, this piece of info is logged to the console. Finally, running this should end up with this result: My Extended Implementation When I checked Microsoft’s implementation, I found some concerns. Therefore, I decided to do some minor changes. IExtendedObservable<out T> using System; using System.Collections.Generic; namespace ExtendedObservable { public interface IExtendedObservable<out T> : IObservable<T> { IReadOnlyCollection<T> Snapshot { get; } IDisposable Subscribe(IObserver<T> observer, bool withHistory); } } What we can notice here: The interface extends the interface. IExtendedObservable<out T> IObservable<T> It is . If you want to know more about this, you can check the article . Covariant Covariance and Contravariance in .NET C# We defined property to allow other modules to get an instant list of already existing info entries without having to subscribe. IReadOnlyCollection<T> Snapshot We also defined method with an extra parameter so that the Observer can decide if it wants to get notified about the already existing info entries or not at the moment of subscribing. IDisposable Subscribe(IObserver<T> observer, bool withHistory) bool withHistory Unsubscriber using System; namespace ExtendedObservable { public class Unsubscriber : IDisposable { private readonly Action m_UnsubscribeAction; private bool m_IsDisposed; public Unsubscriber(Action unsubscribeAction) { m_UnsubscribeAction = unsubscribeAction; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (m_IsDisposed) return; if (disposing) { m_UnsubscribeAction(); } m_IsDisposed = true; } ~Unsubscriber() { Dispose(false); } } } What we can notice here: Now, the class is not generic. Unsubscriber This is because it doesn’t need any more to know the type of the info entity. Instead of having access to the full list of Observers and the Observer it is created for, it just notifies the Observable when it is disposed and the Observable handles the deregistration process by itself. This way, it is doing less than before and it is only doing its job. WeatherForecastUnsubscriber using System; using System.Collections.Generic; namespace ExtendedObservable { public class WeatherForecastUnsubscriber : Unsubscriber { public WeatherForecastUnsubscriber( Action unsubscribeAction) : base(unsubscribeAction) { } } } What we can notice here: We removed the part from . <T> Unsubscriber<T> And now the constructor takes in an to be called in case of disposing. Action WeatherForecast using System; using System.Collections.Generic; namespace ExtendedObservable { public class WeatherForecast : IExtendedObservable<WeatherInfo> { private readonly List<IObserver<WeatherInfo>> m_Observers; private readonly List<WeatherInfo> m_WeatherInfoList; public WeatherForecast() { m_Observers = new List<IObserver<WeatherInfo>>(); m_WeatherInfoList = new List<WeatherInfo>(); } public IReadOnlyCollection<WeatherInfo> Snapshot => m_WeatherInfoList; public IDisposable Subscribe(IObserver<WeatherInfo> observer) { return Subscribe(observer, false); } public IDisposable Subscribe(IObserver<WeatherInfo> observer, bool withHistory) { if (!m_Observers.Contains(observer)) { m_Observers.Add(observer); if (withHistory) { foreach (var item in m_WeatherInfoList) { observer.OnNext(item); } } } return new WeatherForecastUnsubscriber( () => { if (m_Observers.Contains(observer)) { m_Observers.Remove(observer); } }); } public void RegisterWeatherInfo(WeatherInfo weatherInfo) { m_WeatherInfoList.Add(weatherInfo); foreach (var observer in m_Observers) { observer.OnNext(weatherInfo); } } public void ClearWeatherInfo() { m_WeatherInfoList.Clear(); } } } What we can notice here: It is almost the same except for property which returns the internal list but as . IReadOnlyCollection<WeatherInfo> Snapshot m_WeatherInfoList IReadOnlyCollection And method which makes use of the parameter. IDisposable Subscribe(IObserver<WeatherInfo> observer, bool withHistory) withHistory WeatherForecastObserver using System; namespace ExtendedObservable { public class WeatherForecastObserver : IObserver<WeatherInfo> { private IDisposable m_Unsubscriber; public virtual void Subscribe(WeatherForecast provider) { m_Unsubscriber = provider.Subscribe(this, true); } public virtual void Unsubscribe() { m_Unsubscriber.Dispose(); } public void OnCompleted() { Console.WriteLine("Completed"); } public void OnError(Exception error) { Console.WriteLine("Error"); } public void OnNext(WeatherInfo value) { Console.WriteLine($"Temperature: {value.Temperature}"); } } } What we can notice here is that it is almost the same except for which now decides if it should with history or not. Subscribe(WeatherForecast provider) Subscribe Program using System; namespace ExtendedObservable { class Program { static void Main(string[] args) { var provider = new WeatherForecast(); provider.RegisterWeatherInfo(new WeatherInfo(1)); provider.RegisterWeatherInfo(new WeatherInfo(2)); provider.RegisterWeatherInfo(new WeatherInfo(3)); var observer = new WeatherForecastObserver(); observer.Subscribe(provider); provider.RegisterWeatherInfo(new WeatherInfo(4)); provider.RegisterWeatherInfo(new WeatherInfo(5)); observer.Unsubscribe(); provider.RegisterWeatherInfo(new WeatherInfo(6)); observer.Subscribe(provider); provider.RegisterWeatherInfo(new WeatherInfo(7)); Console.ReadLine(); } } } It is the same as before. Finally, running this should end up with the same result as before: What’s Next Now, you know the basics of the in .NET C#. However, this is not the end of the story. Observer Design Pattern There are libraries built on top of and interfaces providing more cool features and capabilities which you might find useful. IObservable<T> IObserver<T> On of these libraries is the library. It consists of a set of extension methods and LINQ standard sequence operators to support asynchronous programming. Reactive Extensions for .NET (Rx) Therefore, I encourage you to explore these libraries and give them a try. I am sure you would like some of them. Also published here.