C# Observer Pattern

Summary: in this tutorial, you’ll learn how to use the C# observer pattern to notify objects (observers) when the state of another object (subject) changes.

Introduction to C# Observer pattern

The Observer pattern defines a one-to-many dependency between objects so that when one object (known as the subject) changes state, all its dependencies known as observers are notified and updated automatically.

The following UML diagram illustrates the Observer pattern:

C# Observer Pattern

Here are the participants in the observer pattern:

  • ISubject provides an interface for subscribing and unsubscribing IObserver objects.
  • IObserver defines an updating interface for objects that should be notified of changes in a subject.
  • ConcreteSubject stores the state of interest to the ConcreteObserver objects. The ConcreteSubject sends a notification to its observers when its state changes.
  • ConcreteObserver has a reference to a ConcreteSubject object. It also implements the IObserver interface to keep its state consistent with the state of the subject.

The ISubject and IObservers objects are linked through a one-to-many relationship. It means that a subject can have multiple observers subscribed to it.

When the state of the ISubject changes, it notifies all of its IObserver objects by calling their Update methods.

The IObserver objects can then access the ISubject‘s new state and update their own state.

C# Observer pattern example

Let’s say you want to develop a program that manages stock quotes. Whenever the price of the stock changes, you want to display it in the console as well as save the data into a file. To do that, you can use the Observer pattern.

First, define an IObserver interface that has a method called Update. The Update method has two parameters symbol and pricer representing the stock’s state:

public interface IObserver
{
    public void Update(string symbol, decimal price);
}Code language: C# (cs)

Second, define the ISubject interface that manages the IObserver objects such as subscribing, unsubscribing, and notifying the IObserver objects:

public interface ISubject
{
    void Subscribe(IObserver observer);
    void Unsubscribe(IObserver observer);
    void NotifyObservers();
}Code language: C# (cs)

Third, define the Stock class that implements the ISubject interface:

public class Stock : ISubject
{
    public string Symbol { get; set; }

    private decimal _price;
    public decimal Price
    {
        get => _price;
        set
        {
            if (_price != value)
            {
                _price = value;
                NotifyObservers();
            }

        }
    }
    public Stock(string symbol, decimal price)
    {
        Symbol = symbol;
        Price = price;
    }

    private readonly List<IObserver> _observers = new();
    public void NotifyObservers() => _observers.ForEach(observer => observer.Update(Symbol, Price));
    public void Subscribe(IObserver observer) => _observers.Add(observer);
    public void Unsubscribe(IObserver observer) => _observers.Remove(observer);
}Code language: C# (cs)

The Stock class has two properties Symbol and Price that represent stock symbol and price respectively.

The Stock class maintains a list of IObserver objects as a List<IObserver>. Since the Stock class implements the ISubject interface, it needs to provide implementations for the Subscribe, Unsubscribe, and NotifyObservers methods:

  • Subscribe – adds an IObserver object to the _observers list.
  • Unsubscribe – removes an IObserver object from the _observers list.
  • NotifyObservers – notifies IObserver object in the _observers list by iterating the list and calling the Update() method of each object.

In the Price setter, if the price changes, we call the NotifiyObservers method to notify the observers by calling their Update method.

Fourth, define a Display class that displays the stock whenever the price changes. The Display class implements the IObserver interface:

public class Display : IObserver
{
    private readonly ISubject _subject;
    public Display(ISubject subject)
    {
        _subject = subject;
        _subject.Subscribe(this);
    }
    public void Update(string symbol, decimal price)
    {
        Console.WriteLine($"{symbol}: {price}");
    }
}Code language: C# (cs)

Notice that the Display method maintains a member as an ISubject object. In the constructor, it assigns the subject to the _subject member and calls the Subscribe() method of the _subject object to subscribe itself as an observer.

Fifth, define a Logger class that implements the IObserver interface:

public class Logger : IObserver
{
    private readonly ISubject _subject;
    private readonly string _filename;

    public Logger(ISubject subject, string filename)
    {
        _subject = subject;
        _subject.Subscribe(this);

        _filename = filename;
    }

    public void Update(string symbol, decimal price)
    {
        using var streamWriter = File.AppendText(_filename);
        streamWriter.WriteLine($"{symbol}:{price}");
    }
}Code language: C# (cs)

The Logger class is similar to the Display class except that it saves the stock data into a text file specified by the filename.

Finally, define the Program class with the Main() method as the entry point of the program:

public class Program
{
    public static void Main(string[] args)
    {
        // Create a new stock
        var stock = new Stock("ABC", 100m);


        // Create two observers Display & Logger
        var display = new Display(stock);
        var logger = new Logger(stock, "stock.txt");

        // Change the price, both display and logger
        // will be notified and updated
        stock.Price += 2;
        stock.Price -= 1;

        Console.ReadLine();

        // remove the logger from the observer list
        stock.Unsubscribe(logger);

        // Change the price, only the display is notified 
        // and updated
        stock.Price += 3;

        Console.ReadLine();
    }
}Code language: C# (cs)

In the Main() method, we create a stock object as the subject and the Display and Logger objects as the observers.

We then change the price of the stock twice, which updates the Display and Logger objects. As a result, you’ll see the console display the stock with new prices:

ABC: 102
ABC: 101
Press Enter (or Return) to remove the Logger from the observer listCode language: C# (cs)

and the stock.txt file will have two entries.

If you hit the Enter (or Return key), the stock removes the Logger object from its observer list and changes its price. At this point, only the Display object is notified and updated:

ABC: 102
ABC: 101
Press Enter (or Return) to remove the Logger from the observer list

ABC: 104Code language: C# (cs)

Because the Logger is not in the observer list, it is not notified and updated. Hence, the stock.txt has no new entries.

Summary

  • Use the C# Observer pattern to define a one-to-many dependency between objects, so that when one object changes, all its dependents are notified and updated automatically.
Was this tutorial helpful ?