In this article, we are going to talk about a behavioral design pattern, the Observer Pattern. We are going to learn what problem this pattern solves and how we can implement it in C#.

To download the source code for this article, you can visit our GitHub repository.

If you want to read more about design patterns in C#, you can inspect our C# Design Patterns page.

Let’s start.

About the Observer Design Pattern

The Observer design pattern allows us to establish a notification mechanism between objects. It enables multiple objects to subscribe to another object and get notified when an event occurs to this observed object. So, on one hand, we have a Provider (sometimes called a Subject or a Publisher) which is the observed object. On the other hand, there are one or more Observers, which are objects subscribing to the Provider. An Observer can subscribe to a Provider and get notified whenever a predefined condition happens. This predefined condition is usually an event or a state change.  

When to Use Observer Design Pattern

This pattern is helpful whenever we want to implement some kind of distributed notification system within our application. Let’s say we have an e-commerce application, where some customers are interested in the products of a particular seller. So, instead of checking for new products every now and then, they can just subscribe to the seller and receive real-time updates. The Observer pattern allows us to do so in a distributed manner, where any customer can subscribe or unsubscribe to any seller. 

Implementing Observer Design Pattern in C#

C# has two powerful interfaces that are designed particularly to implement the Observer pattern: IObserver<T> and IObservable<T>. For an object to be a Provider it has to implement IObservable<T>, while to be an Observer it has to implement IObserver<T>; where T is the type of notification object sent from the Provider to its Observers.

Let’s say we are developing a submission system for a company where applicants can apply for jobs. So, we want to notify HR specialists whenever a new applicant applies for a job. 

To start, let’s define our first main model; Application:

public class Application
{
    public int JobId { get; set; }
    public string ApplicantName { get; set; }

    public Application(int jobId, string applicantName)
    {
        JobId = jobId;
        ApplicantName = applicantName;
    }
}

Next, let’s define HRSpecialist class: 

public class HRSpecialist
{
    public string Name { get; set; }
    public List<Application> Applications { get; set; }

    public HRSpecialist(string name)
    {
        Name = name;
        Applications = new();
    }

    public void ListApplications()
    {
        if(Applications.Any())
            foreach (var app in Applications)
                Console.WriteLine($"Hey, {Name}! {app.ApplicantName} has just applied for job no. {app.JobId}");
        else
            Console.WriteLine($"Hey, {Name}! No applications yet.");
    }
}

This class has a list of applications and a method that lists all these applications if any. 

To continue, let’s create another class that will act as a repository for the applications:

public class ApplicationsHandler 
{
    private readonly List<IObserver<Application>> _observers;
    public List<Application> Applications { get; set; }

    public ApplicationsHandler()
    {
        _observers = new();
        Applications = new();
    }
}

This class, which will act as our Provider, maintains two collections:

  • Applications – a list of the submitted applications
  • _observers – a list of objects that will receive a notification when we receive a new application

Implementing the Provider

As we’ve mentioned, the Provider has to implement IObservable<Application>. So let’s do it by modifying the ApplicationsHandler class:

public class ApplicationsHandler : IObservable<Application>
{
    private readonly List<IObserver<Application>> _observers;
    public List<Application> Applications { get; set; }

    public ApplicationsHandler()
    {
        _observers = new();
        Applications = new();
    }

    public IDisposable Subscribe(IObserver<Application> observer)
    {
        if (!_observers.Contains(observer))
        {
            _observers.Add(observer);

            foreach (var item in Applications)
                observer.OnNext(item);
        }

        return new Unsubscriber(_observers, observer);
    }

    public void AddApplication(Application app)
    {
        Applications.Add(app);

        foreach (var observer in _observers)
            observer.OnNext(app);
    }

    public void CloseApplications()
    {
        foreach (var observer in _observers)
            observer.OnCompleted();

        _observers.Clear();
    }
}

We implement the Subscribe method, which takes a reference to an IObserver<Application> implementation, adds it to the Observers collection, and provides it with existing data by calling its OnNext method. The Subscribe method returns an IDisposable implementation that allows the observer to unsubscribe. We are going to create the Unsubscriber class in a moment. 

We also create the AddApplication method, which adds an application and notifies each observer by calling its OnNext method. The CloseApplications method calls OnCompleted of each observer to notify them that there are no more upcoming applications.

Our Provider is almost ready to send notifications, let’s just define Unsubscriber:

public class Unsubscriber : IDisposable
{
    private readonly List<IObserver<Application>> _observers;
    private readonly IObserver<Application> _observer;

    public Unsubscriber(List<IObserver<Application>> observers, IObserver<Application> observer)
    {
        _observers = observers;
        _observer = observer;
    }

    public void Dispose()
    {
        if (_observers.Contains(_observer))
            _observers.Remove(_observer);
    }
}

Our Provider is ready. Now, let’s configure HRSpecialist to be able to subscribe to ApplicationsHandler.

Implementing the Observer

Let’s implement IObserver<Application> by adding a single field and a few more methods to the Observer HRSpecialist class: 

public class HRSpecialist : IObserver<Application>
{
    private IDisposable _cancellation;

    // previous code

    public virtual void Subscribe(ApplicationsHandler provider)
    {
        _cancellation = provider.Subscribe(this);
    }

    public virtual void Unsubscribe()
    {
        _cancellation.Dispose();
        Applications.Clear();
    }

    public void OnCompleted()
    {
        Console.WriteLine($"Hey, {Name}! We are not accepting any more applications");
    }

    public void OnError(Exception error)
    {
        // This is called by the provider if any exception is raised, no need to implement it here
    }

    public void OnNext(Application value)
    {
        Applications.Add(value);
    }
}

We implement the interface’s three methods: OnNext which receives the notification,  OnError which handles any exception raised, and OnCompleted which indicates that there are no more upcoming notifications. These methods are called by the Provider, as we’ve seen in ApplicationsHandler.  

We also add Subscribe method, which calls the Provider’s Subscribe method and assigns the returned Unsubscriber object to _cancellation. This is used by the Unsubscriber method to unsubscribe.

That’s it, we are ready to test our implementation. Let’s head to Main and play around with it:

class Program
{
    static void Main(string[] args)
    {
        var observer1 = new HRSpecialist("Bill");
        var observer2 = new HRSpecialist("John");

        var provider = new ApplicationsHandler();

        observer1.Subscribe(provider);
        observer2.Subscribe(provider);
        provider.AddApplication(new(1, "Jesus"));
        provider.AddApplication(new(2, "Dave"));

        observer1.ListApplications();
        observer2.ListApplications();

        observer1.Unsubscribe();

        Console.WriteLine();
        Console.WriteLine($"{observer1.Name} unsubscribed");
        Console.WriteLine();

        provider.AddApplication(new(3, "Sofia"));

        observer1.ListApplications();
        observer2.ListApplications();

        Console.WriteLine();

        provider.CloseApplications();
    }
}

We create a provider and two observers: Bill and John. Both subscribe to the provider. We add some applications and expect both observers to receive them as well. Bill unsubscribes, he will not receive any more applications. Finally, the provider stops receiving applications, notifies the subscribers (only John in this case), and cancels all subscriptions. 

The final output demonstrates this mechanism:

Hey, Bill! Jesus has just applied for job no. 1
Hey, Bill! Dave has just applied for job no. 2
Hey, John! Jesus has just applied for job no. 1
Hey, John! Dave has just applied for job no. 2

Bill unsubscribed

Hey, Bill! No applications yet.
Hey, John! Jesus has just applied for job no. 1
Hey, John! Dave has just applied for job no. 2
Hey, John! Sofia has just applied for job no. 3

Hey, John! We are not accepting any more applications

Conclusion

In this article, we have learned why and when to use Observer Design Pattern and we have learned how to implement it with our example app using C#.