top of page

Subscribe to get best practices, tutorials, and many other cool things directly to your email inbox

Writer's pictureAhmed Tarek

Observer Design Pattern in .NET C#

Updated: Apr 17

Learn about the Observer Design Pattern in .NET C# with some enhancements.


Observer Design Pattern in DotNet (.NET) CSharp (C#) with enhancements. Subject Observable Provider Covariance Contravariance Best Practice Coding Programming Software Engineering Architecture Development
Photo by Maarten van den Heuvel on Unsplash, adjusted by Ahmed Tarek

Observer Design Pattern Definition


The Observer Design Pattern is one of the most important and commonly used design patterns, and this is for a reason.


First, let’s check the formal definition of the Observer Design Pattern.


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 System.IObservable<T> and System.IObserver<T> interfaces. The generic type parameter represents the type that provides notification information.

So, from the definition above, we can understand the following:

  1. We have two parties or modules.

  2. The module which has some stream of information to provide. This module is called Provider (as it provides information), or Subject (as it subjects information to outside world), or Observable (as it could be observed by outside world).

  3. The module which is interested into a stream of information coming from somewhere else. This module is called Observer (as it observes information).


 

Advantages of Observer Design Pattern


As we now know, the Observer Design Pattern formulates the relation between the Observable and Observer modules. What makes the Observer Design Pattern unique is that using it you can achieve this without having a tightly coupled relation.


Analyzing the way the pattern works, you would find the following:

  1. The Observable knows the minimal information needed about the Observer.

  2. The Observer knows the minimal information needed about the Observable.

  3. Even the mutual knowledge is achieved through abstractions, not concrete implementations.

  4. At the end, both modules can do their job, and only their job.


 


 

Abstractions Used


These are the abstractions used to implement the Observer Design Pattern in .NET C#.



Observable<out T>


This is a Covariant interface representing any Observable. If you want to know more about Variance in .NET, you can check the article Covariance and Contravariance in .NET C#.


Members defined in this interface are:

public IDisposable Subscribe (IObserver<out T> observer);

The Subscribe method should be called to inform the Observable that some Observer is interested into its stream of information.


The Subscribe method returns an object which implements the IDisposable interface. This object could then be used by the Observer to unsubscribe from the stream of information provided by the Observable. Once this is done, the Observer would not be notified about any updates to the stream of information.



IObserver<in T>


This is a Contravariant interface representing any Observer. If you want to know more about Variance in .NET, you can check the article 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 OnCompleted method should be called by the Observable to inform the Observer that the stream of information is completed and the Observer should not expect any more information.


The OnError method should be called by the Observable to inform the Observer that an error has occurred.


The OnNext method should be called by the Observable to inform the Observer that a new piece of info is ready and is being added to the stream.


 

Microsoft’s Implementation


Now, let’s see how Microsoft recommends implementing the Observer Design Pattern in C#. Later, I will show you some minor enhancements I implemented myself.


We will build a simple Weather Forecast Console Application. In this application, we will have WeatherForecast module (Observable, Provider, Subject) and WeatherForecastObserver module (Observer).


So, let’s begin looking into the implementation.



WeatherInfo



This is the entity representing the piece of information to be flowing in the information stream.



WeatherForecast



What we can notice here:

  1. The WeatherForecast class is implementing IObservable<WeatherInfo>.

  2. In the implementation of the Subscribe method, we check if the passed in Observer was already registered before or not. If not, we add it to the local m_Observers observers list. Then, we loop on all the WeatherInfo entries we have in the local m_WeatherInfoList list one by one and inform the Observer about it by calling the OnNext method of the Observer.

  3. Finally, we return a new instance of WeatherForecastUnsubscriber class to be used by the Observer for unsubscribing from the information stream.

  4. The RegisterWeatherInfo method is defined so that the main module can register new WeatherInfo. In the real world, this could be replaced by an internal scheduled API call or a listener to a SignalR Hub or something else that would act as a source of information.



Unsubscriber<T>



What we can notice here:

  1. This is a base class for any Un-subscriber.

  2. It implements IDisposable by applying the Disposable Design Pattern.

  3. Through the constructor, it takes in the full list of Observers and the Observer it is created for.

  4. While disposing, it checks if the Observer already exists into the full list of Observers. If yes, it removes it from the list.



WeatherForecastUnsubscriber



What we can notice here:

  1. This is inheriting from Unsubscriber<T> class.

  2. No special handling is happening.



WeatherForecastObserver



What we can notice here:

  1. The WeatherForecastObserver class is implementing IObserver<WeatherInfo>.

  2. On the OnNext method, we are writing the temperature to the console.

  3. On the OnCompleted method, we are writing “Completed” to the console.

  4. On the OnError method, we are writing “Error” to the console.

  5. We defined void Subscribe(WeatherForecast provider) 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.

  6. Using the same concept, the void Unsubscribe() method is defined and it makes use of the internally saved un-subscriber object.



Program



What we can notice here:

  1. We created an instance of the provider.

  2. Then registered 3 pieces of info.

  3. Up to this moment, nothing should be logged to the console as no observers are defined.

  4. Then created an instance of the observer.

  5. Then subscribed the observer to the stream.

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

  7. Then we register 2 pieces of info.

  8. So, we get 2 more messages logged to the console.

  9. Then we unsubscribe.

  10. Then we register 1 piece of info.

  11. However, this piece of info would not be logged to the console as the observer had already unsubscribed.

  12. Then the observer subscribes again.

  13. Then we register 1 piece of info.

  14. So, this piece of info is logged to the console.


Finally, running this should end up with this result:


Observer Design Pattern in DotNet (.NET) CSharp (C#) with enhancements. Subject Observable Provider Covariance Contravariance Best Practice Coding Programming Software Engineering Architecture Development

 


 

My Extended Implementation

When I checked Microsoft’s implementation, I found some concerns. Therefore, I decided to do some minor changes.



IExtendedObservable<out T>



What we can notice here:

  1. The IExtendedObservable<out T> interface extends the IObservable<T> interface.

  2. It is Covariant. If you want to know more about this, you can check the article Covariance and Contravariance in .NET C#.

  3. We defined IReadOnlyCollection<T> Snapshot property to allow other modules to get an instant list of already existing info entries without having to subscribe.

  4. We also defined IDisposable Subscribe(IObserver<T> observer, bool withHistory) method with an extra bool withHistory 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.



Unsubscriber



What we can notice here:

  1. Now, the Unsubscriber class is not generic.

  2. This is because it doesn’t need any more to know the type of the info entity.

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

  4. This way, it is doing less than before and it is only doing its job.



WeatherForecastUnsubscriber



What we can notice here:

  1. We removed the <T> part from Unsubscriber<T>.

  2. And now the constructor takes in an Action to be called in case of disposing.



WeatherForecast



What we can notice here:

  1. It is almost the same except for IReadOnlyCollection<WeatherInfo> Snapshot property which returns the internal m_WeatherInfoList list but as IReadOnlyCollection.

  2. And IDisposable Subscribe(IObserver<WeatherInfo> observer, bool withHistory) method which makes use of the withHistory parameter.



WeatherForecastObserver



What we can notice here is that it is almost the same except for Subscribe(WeatherForecast provider) which now decides if it should Subscribe with history or not.



Program



It is the same as before.


Finally, running this should end up with the same result as before:


Observer Design Pattern in DotNet (.NET) CSharp (C#) with enhancements. Subject Observable Provider Covariance Contravariance Best Practice Coding Programming Software Engineering Architecture Development

 

Observer Design Pattern in DotNet (.NET) CSharp (C#) with enhancements. Subject Observable Provider Covariance Contravariance Best Practice Coding Programming Software Engineering Architecture Development
Photo by Emily Morter on Unsplash, adjusted by Ahmed Tarek

What’s Next


Now, you know the basics of the Observer Design Pattern in .NET C#. However, this is not the end of the story.


There are libraries built on top of IObservable<T> and IObserver<T> interfaces providing more cool features and capabilities which you might find useful.


On of these libraries is the Reactive Extensions for .NET (Rx) library. It consists of a set of extension methods and LINQ standard sequence operators to support asynchronous programming.


Therefore, I encourage you to explore these libraries and give them a try. I am sure you would like some of them.



Recent Posts

See All

Komentar

Dinilai 0 dari 5 bintang.
Belum ada penilaian

Tambahkan penilaian

Subscribe to get best practices, tutorials, and many other cool things directly to your email inbox

bottom of page
Mastodon Mastodon