Click here to Skip to main content
15,882,113 members
Articles / General Programming / Architecture

Implement Observer Pattern in .NET (3 Techniques)

Rate me:
Please Sign up or sign in to vote.
4.99/5 (33 votes)
30 Dec 2018CPOL9 min read 58.2K   643   61   17
Multiple techniques of implementing The Observer Pattern in .NET

Introduction

We've all read about the Design patterns through various years of our Software Development Career. Of all the patterns, the coolest and the most practical I have come across is The Observer Pattern a.k.a. Publisher-Subscriber Pattern. You'll find various articles on how to implement the observer pattern in .NET Framework using the assemblies provided in the framework. BUT, .NET Framework has evolved over the years and along with that, it has also been providing new libraries for creating the Observer Pattern.

For instance, before .NET 4.0, we had to write custom code to implement Observer Pattern or use delegates and events. With the release of framework 4.0 came the very cool IObservable and IObserver interfaces for implementing Observer Pattern. We'll go through all these techniques with code examples in this article.

Background

To give you all a brief idea of the Observer Pattern, it defines a one-to-many dependency between objects (Publisher and multiple Subscribers) so that when one object (Publisher) changes state, all the dependents (Subscriber) are notified and updated automatically.

The above definition is highly influenced from the Book "Head First Design Patterns". I would strongly recommend anyone beginning to learn Design Patterns to read this book.

Sample Scenario

The code below is based on a scenario wherein we have a weather station which records weather data (temperature, humidity and pressure).

This data has to be consumed by multiple displays and displayed accordingly. Everytime there is new data available to the weather station, it should "push" the data to the displays which should all be updated accordingly. Suppose we have 3 displays (Current Conditions, Statistics Display and Forecast Display), all of these have to be updated whenever new data is available at the Weather Station.

The scenario is similar to the one presented in the Book "Head First Design Patterns", BUT here, we'll be discussing the .NET implementation of the same.

Implementation

So the Weather Data object is actually an object having 3 properties - temperature, humidity and pressure.

The WeatherData class looks like the one below:

C#
public class WeatherData
    {
        public float Temperature { get; set; }
        public float Humidity { get; set; }
        public float Pressure { get; set; }

        public WeatherData(float temp, float humid, float pres)
        {
            Temperature = temp;
            Humidity = humid;
            Pressure = pres;
        }
    }
}

Now, this piece of data needs to be pushed to the weather displays (subscribers) whenever new data is available at the weather station.

Our code samples here would focus on implementing the observer pattern pre-.NET 4.0 and post .NET 4.0 .

The code in each section would be categorized into Publisher Code (Classes and Interfaces) and Subscriber Code (Classes and Interfaces). So let's get on with it.

Observer Pattern (before .NET 4.0)

<Technique #1>

Using Pure Object Oriented (OO) Programming Concepts

As per OO Best practices, we should always try to program to interfaces and not implementation. What follows is based on that.

Publisher

The Publisher here is actually a Weather Data Provider which provides the weather data. This WeatherDataProvider Class implements the IPublisher interface as shown below:

C#
public interface IPublisher
    {
        void RegisterSubscriber(ISubscriber subscriber);
        void RemoveSubscriber(ISubscriber subscriber);
        void NotifySubscribers();
    }

The three methods above do the following:

  • RegisterSubscriber - Registers a new subscriber with the Publisher. The publisher has to add the subscribers to the list of subscribers it has to notify whenever WeatherData has changed.
  • RemoveSubscriber - Removes a registered subscriber from the publisher's notification list
  • NotifySubscibers - This method actually invokes a method on the subscriber object to notify that some new WeatherData is available

The concrete implementation of a WeatherDataProvider would be as below:

C#
public class WeatherDataProvider : IPublisher
    {
        List<ISubscriber> ListOfSubscribers;
        WeatherData data;
        public WeatherDataProvider()
        {
            ListOfSubscribers = new List<ISubscriber>();
        }
        public void RegisterSubscriber(ISubscriber subscriber)
        {
            ListOfSubscribers.Add(subscriber);
        }

        public void RemoveSubscriber(ISubscriber subscriber)
        {
            ListOfSubscribers.Remove(subscriber);
        }

        public void NotifySubscribers()
        {
            foreach (var sub in ListOfSubscribers)
            {
                sub.Update(data);
            }
        }

        private void MeasurementsChanged()
        {
            NotifySubscribers();
        }
        public void SetMeasurements(float temp, float humid, float pres)
        {
            data = new WeatherData(temp, humid, pres);           
            MeasurementsChanged();
        }
    }

Subscriber

The subscribers here are actually the weather displays, which consume the data. Each subscriber should implement the ISubscriber interface.

C#
public interface ISubscriber
    {
        void Update(WeatherData data);
    }

The Subscriber interface just has once method; It displays the current WeatherData received from the WeatherDataProvider.

An implementation of the CurrentConditionsDisplay is as under:

C#
public class CurrentConditionsDisplay : ISubscriber
    {
        WeatherData data;
        IPublisher weatherData;

        public CurrentConditionsDisplay(IPublisher weatherDataProvider)
        {
            weatherData = weatherDataProvider;
            weatherData.RegisterSubscriber(this);
        }

        public void Display()
        {
            Console.WriteLine("Current Conditions : 
            Temp = {0}Deg | Humidity = {1}% | 
            Pressure = {2}bar", data.Temperature, data.Humidity, data.Pressure);
        }
       
        public void Update(WeatherData data)
        {
            this.data = data;
            Display();
        }
    }

In the above code, we have done a Dependency Injection or IoC wherein we have injected the IPublisher interface via the Constructor. Constructor Injection is a very common practice in OO programming.

So, what happens in the above code is that when the display in instantiated, it makes a call to the RegisterSubscriber method of the WeatherDataProvider and registers itself as an interested subscriber.

If a display wants to unregister itself, it has to call the RemoveSubscriber method of the WeatherDataProvider. There are multiple ways of accomplishing it - you can make a call to RemoveSubscriber in the Destructor (as above) or can implement IDisposable and make a call to RemoveSubscriber in Dispose method, or just simply make a class method and make a call there.

Like CurrentConditionsDisplay, we may have a ForecastDisplay as well (as shown below):

C#
public class ForecastDisplay : ISubscriber, IDisposable
    {
        WeatherData data;
        IPublisher weatherData;

        public ForecastDisplay(IPublisher weatherDataProvider)
        {
            weatherData = weatherDataProvider;
            weatherData.RegisterSubscriber(this);
        }

        public void Display()
        {
            Console.WriteLine("Forecast Conditions : Temp = {0}Deg | 
                Humidity = {1}% | Pressure = {2}bar", data.Temperature + 6, 
                data.Humidity + 20, data.Pressure - 3);
        }

        public void Update(WeatherData data)
        {
            this.data = data;
            Display();
        }

        public void Dispose()
        {
            weatherData.RemoveSubscriber(this);
        }
    }

Similarly, you could define any number of displays here, and they could subscribe to the WeatherStation for updates.

For demonstrating the above piece of code, we'll make a sample Console Application as below:

C#
class Program
    {
        static void Main(string[] args)
        {
            WeatherDataProvider weatherData = new WeatherDataProvider();

            CurrentConditionsDisplay currentDisp = new CurrentConditionsDisplay(weatherData);            
            ForecastDisplay forecastDisp = new ForecastDisplay(weatherData);

            weatherData.SetMeasurements(40, 78, 3);
            Console.WriteLine();
            weatherData.SetMeasurements(45, 79, 4);
            Console.WriteLine();
            weatherData.SetMeasurements(46, 73, 6);

            //Remove forecast display
            forecastDisp.Dispose();
            Console.WriteLine();
            Console.WriteLine("Forecast Display removed");
            Console.WriteLine();
            weatherData.SetMeasurements(36, 53, 8);

            Console.Read();
        }
    }

The output should be as under:

Image 1

<Technique #2>

Using Events and Delegates

Now, onto the second way of implementing the observer pattern in .NET. The first method, of course, didn't make use of any of the .NET libraries for implementing the pattern.

This technique is very common in .NET whenever it comes to implement the Observer pattern. It makes use of Events and delegates in .NET framework to accomplish the same.

I would not delve much into the details of the below source code as most of it would be self explanatory, but if you do need more information on the below implementation, you could refer to this MSDN link.

To start with, this technique makes use of the generic EventHandler<T> delegate to achieve the pattern. The basic theory is pretty straightforward:

  • The publisher has a public Event and EventHandler which it raises each time the WeatherData changes.
  • The subscribers attach to the EventHandler to get notified whenever the event is raised.
  • The EventArgs e, received at the subscriber contain data about the current weather conditions.

To begin with, first, we have to create a WeatherEventArgs class which inherits from EventArgs:

C#
public class WeatherDataEventArgs : EventArgs
   {
       public WeatherData data { get; private set; }
       public WeatherDataEventArgs(WeatherData data)
       {
           this.data = data;
       }
   }

This class contains the Weather Data which will be passed as Event parameters in the Event handler.

Publisher

The Publisher here is a class called WeatherDataProvider which raises events whenever weather data changes and notifies the subscribed displays:

C#
public class WeatherDataProvider : IDisposable
    {
        public event EventHandler<WeatherDataEventArgs> RaiseWeatherDataChangedEvent;

        protected virtual void OnRaiseWeatherDataChangedEvent(WeatherDataEventArgs e)
        {
            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised.
            EventHandler<WeatherDataEventArgs> handler = RaiseWeatherDataChangedEvent;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        public void NotifyDisplays(float temp, float humid, float pres)
        {
            OnRaiseWeatherDataChangedEvent
                 (new WeatherDataEventArgs(new WeatherData(temp, humid, pres)));
        }

        public void Dispose()
        {
            if (RaiseWeatherDataChangedEvent != null)
            {
                foreach (EventHandler<WeatherDataEventArgs> 
                item in RaiseWeatherDataChangedEvent.GetInvocationList())
                {
                    RaiseWeatherDataChangedEvent -= item;
                }
            }
        }
    }

The above code declares an EventHandler delegate of generic type which takes in WeatherDataEventArgs as arguments to be supplied to event handlers.

Each time the NotifyDisplays method is called, the OnRaiseWeatherDataChangedEvent is raised which in turn calls the event handlers in the respective Displays (subscribers) in the following code.

Subscriber

The implementation of the CurrentConditionsDisplay is as given below. We can have similarly any number of displays attaching to the Event via Event handlers.

C#
public class CurrentConditionsDisplay
   {
       WeatherData data;
       WeatherDataProvider WDprovider;

       public CurrentConditionsDisplay(WeatherDataProvider provider)
       {
           WDprovider = provider;
           WDprovider.RaiseWeatherDataChangedEvent += provider_RaiseWeatherDataChangedEvent;
       }

       void provider_RaiseWeatherDataChangedEvent(object sender, WeatherDataEventArgs e)
       {
           data = e.data;
           UpdateDisplay();
       }

       public void UpdateDisplay()
       {
           Console.WriteLine("Current Conditions : Temp = {0}Deg |
           Humidity = {1}% | Pressure = {2}bar", data.Temperature, data.Humidity, data.Pressure);
       }

       public void Unsubscribe()
       {
           WDprovider.RaiseWeatherDataChangedEvent -= provider_RaiseWeatherDataChangedEvent;
       }
   }

Like the one above, we could also have a ForecastDisplay (available in source code).

A demo of the usage of the above can be done using a Console Application with code as under:

C#
class Program
    {
        static void Main(string[] args)
        {
            WeatherDataProvider provider = new WeatherDataProvider();

            CurrentConditionsDisplay current = new CurrentConditionsDisplay(provider);            
            ForecastDisplay forecast = new ForecastDisplay(provider);

            provider.NotifyDisplays(40, 78, 3);
            Console.WriteLine();
            provider.NotifyDisplays(42, 68, 5);
            Console.WriteLine();
            provider.NotifyDisplays(45, 68, 8);
            Console.WriteLine();
            forecast.Unsubscribe();
            Console.WriteLine("Forecast Display removed");
            Console.WriteLine();
            provider.NotifyDisplays(30, 58, 1);

            //Code to call to detach all event handler
            provider.Dispose();

            Console.Read();
        }
    }

Observer Pattern (.NET 4.0 and above)

When .NET framework was released, it got with it a myriad of requested language features for C# like dynamic type, optional parameters, IObservable, etc. Some of these were features which were available in other languages/frameworks, BUT not in C#.

One of the most notable of them all was IObservable<T> and IObserver<T>. These have been available in Java before it came to C# and it is indeed one of my most favourites features. These Collections bring in some unique and nifty functionality from us developers to exploit. The most important being able to implement a robust Publisher-Subscriber model, a.k.a., Observer Pattern.

The below implementation is in line with the sample implementation in the MSDN link here.

<Technique #3>

Using IObservable<T> and IObserver<T>

These libraries make it the easiest to implement an Observer Pattern. In fact, it's so easy that you might not even realise that you are using a Publisher-Subsriber model in your program while using these libraries.

In the generic Type IObservable<T> and IObserver<T>, the T in our case would be WeatherData.

Publisher

The publisher (WeatherDataProvider) has to just implement the IObservable<T> interface (below).

C#
public class WeatherDataProvider : IObservable<WeatherData>
    {
        List<IObserver<WeatherData>> observers;

        public WeatherDataProvider()
        {
            observers = new List<IObserver<WeatherData>>();
        }

        public IDisposable Subscribe(IObserver<WeatherData> observer)
        {
            if (!observers.Contains(observer))
            {
                observers.Add(observer);
            }
            return new UnSubscriber(observers, observer);
        }

        private class UnSubscriber : IDisposable
        {
            private List<IObserver<WeatherData>> lstObservers;
            private IObserver<WeatherData> observer;

            public UnSubscriber(List<IObserver<WeatherData>> ObserversCollection, 
                                IObserver<WeatherData> observer)
            {
                this.lstObservers = ObserversCollection;
                this.observer = observer;
            }

            public void Dispose()
            {
                if (this.observer != null)
                {
                    lstObservers.Remove(this.observer);
                }
            }
        }

        private void MeasurementsChanged(float temp, float humid, float pres)
        {
            foreach (var obs in observers)
            {
                obs.OnNext(new WeatherData(temp, humid, pres));
            }
        }

        public void SetMeasurements(float temp, float humid, float pres)
        {
            MeasurementsChanged(temp, humid, pres);
        }
    }

Observers register to receive notifications by calling its IObservable<T>.Subscribe method, which assigns a reference to the observer object to a private generic List<T> object. The method returns an Unsubscriber object, which is an IDisposable implementation that enables observers to stop receiving notifications. The Unsubscriber class is a simple nested class that implements IDisposable and also keeps a list of Subscribed users and is used by the observers (Displays in our case), to unsubscribe. We'll know more about that in the next section.

Also, there is a function OnNext which we have invoked on the subscriber/observer. This function is actually an inbuilt function of IObserver which indicates that something has changed in the collection. This is actually the function which notifies the subscribers of changes.

Apart from OnNext, there is OnError and OnCompleted functions as well. We'll discuss all of these in the next section.

Subscriber

The subscriber (Displays) have to just implement the IObserver<T> interface. The implementation of the CurrentConditionsDisplay is as under. We can have similarly any number of displays (see source code):

C#
public class CurrentConditionsDisplay : IObserver<WeatherData>
    {
        WeatherData data;
        private IDisposable unsubscriber;

        public CurrentConditionsDisplay()
        {

        }
        public CurrentConditionsDisplay(IObservable<WeatherData> provider)
        {
            unsubscriber = provider.Subscribe(this);
        }
        public void Display()
        {
            Console.WriteLine("Current Conditions : Temp = {0}Deg | 
            Humidity = {1}% | Pressure = {2}bar", data.Temperature, data.Humidity, data.Pressure);
        }

        public void Subscribe(IObservable<WeatherData> provider)
        {
            if (unsubscriber == null)
            {
                unsubscriber = provider.Subscribe(this);
            }
        }

        public void Unsubscribe()
        {
            unsubscriber.Dispose();
        }

        public void OnCompleted()
        {
            Console.WriteLine("Additional temperature data will not be transmitted.");
        }

        public void OnError(Exception error)
        {
            Console.WriteLine("Some error has occurred..");
        }

        public void OnNext(WeatherData value)
        {
            this.data = value;
            Display();
        }
    }

Things of interest in the above code are as follows:

  • When you make a call to subscriber method, it returns an object which implements IDisposable (in this case Unsubscriber). So when we call Dispose on that object, it automatically calls Unsubscribe.
  • There are 3 methods which can be invoked by the Publisher/provider on the subscriber/observers :
    • IObserver<T>.OnNext method to pass the observer a T object that has current data, changed data, or fresh data.
    • IObserver<T>.OnError method to notify the observer that some error condition has occurred (Note that this does NOT get automatically called whenever an exception occurs in the provider. Its actually the responsibility of the programmer to catch the exception in the provider and then invoke this function).
    • IObserver<T>.OnCompleted method to notify the observer that it has finished sending notifications

A demo program to watch this code in action can be made using a Console application with code as below:

C#
class Program
   {
       static void Main(string[] args)
       {
           WeatherDataProvider weatherDataO = new WeatherDataProvider();

           CurrentConditionsDisplay currentDisp = new CurrentConditionsDisplay(weatherDataO);

           ForecastDisplay forecastDisp = new ForecastDisplay(weatherDataO);

           weatherDataO.SetMeasurements(40, 78, 3);
           Console.WriteLine();
           weatherDataO.SetMeasurements(45, 79, 4);
           Console.WriteLine();
           weatherDataO.SetMeasurements(46, 73, 6);
           //Remove forecast display
           forecastDisp.Unsubscribe();

           Console.WriteLine();
           Console.WriteLine("Forecast Display removed");
           Console.WriteLine();
           weatherDataO.SetMeasurements(36, 53, 8);

           Console.Read();
       }
   }

Points of Interest

When I started studying up Design Patterns and especially the Observer Pattern, I discovered that there can be numerous ways of implementing the same in .NET Framework.

After some research, I have made peace with my curiosity and settled on the above 3 implementation techniques. Of course, you could have another cool implementation of the Observer pattern and I'll be happy to know about that. Sound off in the comments below.

History

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United States United States
I am a Developer working across multiple Technologies like .NET, Scala, Angular and NodeJS.
Passionate about WEB technologies more than anything.
Fascinated about the idea of how the WEB world and the WinForm world are slowly merging into each other, with a very thin line to differentiate them.

I am an active blogger and I blog mostly about Technology. Also, I have presented at certain points in the past on technical topics.

My Blog


Life & Tech

Programming Community Profiles


Stack Overflow
GitHub

Presentations


Slideshare

Social Profiles


Facebook | Twitter | LinkedIn | Google+

Comments and Discussions

 
QuestionGood example Pin
James Lonero1-Jan-19 16:50
James Lonero1-Jan-19 16:50 
QuestionTechnique #4: Implement INotifyPropertyChanged and INotifyCollectionChanged Pin
Member 1200388031-Dec-18 2:24
professionalMember 1200388031-Dec-18 2:24 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA13-Aug-14 3:14
professionalȘtefan-Mihai MOGA13-Aug-14 3:14 
GeneralRe: My vote of 5 Pin
Chinmoy Mohanty13-Aug-14 18:39
Chinmoy Mohanty13-Aug-14 18:39 
GeneralMy vote of 5 Pin
Halil ibrahim Kalkan22-Jul-14 5:57
Halil ibrahim Kalkan22-Jul-14 5:57 
GeneralMy vote of 5 Pin
Humayun Kabir Mamun20-Jul-14 21:37
Humayun Kabir Mamun20-Jul-14 21:37 
GeneralrX Pin
Mike Cattle15-Jul-14 11:59
Mike Cattle15-Jul-14 11:59 
GeneralRe: rX Pin
Chinmoy Mohanty15-Jul-14 12:11
Chinmoy Mohanty15-Jul-14 12:11 
QuestionGood Article. Publisher Subscriber Pattern. Pin
umlcat15-Jul-14 10:45
umlcat15-Jul-14 10:45 
QuestionFeels familiar Pin
weedweaver14-Jul-14 1:36
weedweaver14-Jul-14 1:36 
AnswerRe: Feels familiar Pin
Chinmoy Mohanty14-Jul-14 2:08
Chinmoy Mohanty14-Jul-14 2:08 
QuestionFinalize() Pin
Paulo Zemek13-Jul-14 5:51
mvaPaulo Zemek13-Jul-14 5:51 
GeneralMy vote of 5 Pin
CatchExAs13-Jul-14 1:11
professionalCatchExAs13-Jul-14 1:11 
GeneralRe: My vote of 5 Pin
Chinmoy Mohanty13-Jul-14 1:31
Chinmoy Mohanty13-Jul-14 1:31 
QuestionSourceCode? Pin
Johann Krenn13-Jul-14 0:24
Johann Krenn13-Jul-14 0:24 
AnswerRe: SourceCode? Pin
Chinmoy Mohanty13-Jul-14 1:29
Chinmoy Mohanty13-Jul-14 1:29 
GeneralRe: SourceCode? Pin
Johann Krenn13-Jul-14 2:29
Johann Krenn13-Jul-14 2:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.