Observer Pattern

Vaibhav • September 11, 2025

In the previous article, we explored predicate delegates - a powerful way to encapsulate decision-making logic and filter data using functions. Now we’re ready to look at a design pattern that builds on delegates and events to create reactive, loosely coupled systems: the Observer Pattern.

The Observer Pattern is one of the most widely used behavioral design patterns in software development. It allows objects to subscribe to changes in another object and react when those changes occur. In C#, this pattern is implemented naturally using event and delegate constructs, which you’ve already learned about in earlier articles.

In this article, we’ll walk through the structure of the Observer Pattern, explain how it works in C#, and build a complete example step-by-step. You’ll learn how to define subjects and observers, how to raise events, and how to handle notifications in a clean, scalable way.

What Is the Observer Pattern?

The Observer Pattern defines a one-to-many relationship between objects. One object (the subject) maintains a list of dependents (the observers) and notifies them automatically when its state changes.

This pattern is useful when multiple parts of your application need to respond to changes in a shared resource - like a data model, a sensor, or a user interface component. Instead of hardcoding dependencies, you allow observers to subscribe and react independently.

In C#, the subject typically exposes an event, and observers attach handlers using +=. This makes the pattern easy to implement and maintain.

Building the Observer Pattern in C#

Let’s build a simple example: a TemperatureSensor class that notifies observers when the temperature changes. We’ll define a custom EventArgs class, an event in the subject, and multiple observers that respond to the event.

Step 1: Define Custom EventArgs

First, we define a class to hold the temperature data. This will be passed to observers when the event is raised.

public class TemperatureChangedEventArgs : EventArgs
{
    public double NewTemperature { get; }

    public TemperatureChangedEventArgs(double newTemperature)
    {
        NewTemperature = newTemperature;
    }
}

This class inherits from EventArgs and includes a read-only property for the new temperature value.

Step 2: Create the Subject

Next, we define the TemperatureSensor class. It exposes an event called TemperatureChanged and raises it whenever the temperature is updated.

public class TemperatureSensor
{
    private double _temperature;

    public event EventHandler<TemperatureChangedEventArgs> TemperatureChanged;

    public void SetTemperature(double newTemperature)
    {
        if (_temperature != newTemperature)
        {
            _temperature = newTemperature;
            OnTemperatureChanged(newTemperature);
        }
    }

    protected virtual void OnTemperatureChanged(double newTemp)
    {
        TemperatureChanged?.Invoke(this, new TemperatureChangedEventArgs(newTemp));
    }
}

The SetTemperature method updates the internal temperature and raises the event if the value has changed. The OnTemperatureChanged method encapsulates the event invocation logic.

Step 3: Create Observers

Now we define observer classes that subscribe to the sensor’s event and respond when the temperature changes.

public class DisplayUnit
{
    public void Subscribe(TemperatureSensor sensor)
    {
        sensor.TemperatureChanged += OnTemperatureChanged;
    }

    private void OnTemperatureChanged(object sender, TemperatureChangedEventArgs e)
    {
        Console.WriteLine($"Display: Temperature is now {e.NewTemperature}°C");
    }
}

public class AlertSystem
{
    public void Subscribe(TemperatureSensor sensor)
    {
        sensor.TemperatureChanged += OnTemperatureChanged;
    }

    private void OnTemperatureChanged(object sender, TemperatureChangedEventArgs e)
    {
        if (e.NewTemperature > 30)
        {
            Console.WriteLine("Alert: Temperature is too high!");
        }
    }
}

Each observer defines a method that matches the event signature and subscribes to the sensor’s event. The DisplayUnit simply prints the temperature, while the AlertSystem checks for high temperatures and triggers an alert.

Step 4: Wire Everything Together

Finally, we create the sensor and observers, and simulate temperature changes.

var sensor = new TemperatureSensor();
var display = new DisplayUnit();
var alert = new AlertSystem();

display.Subscribe(sensor);
alert.Subscribe(sensor);

sensor.SetTemperature(25); // Output: Display: Temperature is now 25°C
sensor.SetTemperature(32); // Output: Display: Temperature is now 32°C
                           //         Alert: Temperature is too high!

Each time the temperature changes, the sensor notifies all observers. They respond independently based on their own logic. This is the essence of the Observer Pattern - decoupled, reactive behavior.

Unsubscribing Observers

Observers can unsubscribe from events using the -= operator. This is important for memory management and avoiding unwanted notifications.

sensor.TemperatureChanged -= display.OnTemperatureChanged;

After unsubscribing, the DisplayUnit will no longer receive updates. Always unsubscribe when the observer is no longer needed.

Benefits of the Observer Pattern

The Observer Pattern offers several advantages:

It promotes loose coupling - the subject doesn’t need to know who the observers are or what they do. Observers can be added or removed dynamically. The pattern supports modular design, testability, and scalability.

It’s ideal for UI updates, real-time monitoring, event-driven systems, and any scenario where multiple components need to react to changes.

Best Practices

Use EventHandler<T> for consistency and clarity. Always check for null before raising events using ?.Invoke(). Keep event handlers short and focused. Unsubscribe when observers are no longer needed to avoid memory leaks.

Design your observers to be independent and reusable. Avoid hardcoding dependencies between subjects and observers - let events do the communication.

Summary

The Observer Pattern is a powerful way to build reactive systems in C#. It allows objects to subscribe to changes and respond dynamically - without tight coupling or complex dependencies. In this article, you learned how to implement the pattern using events and delegates, how to define subjects and observers, and how to manage subscriptions cleanly.

You now understand how to use the Observer Pattern to build scalable, maintainable applications that respond to change gracefully. In the next article, we’ll wrap up the chapter with Functional Programming Exercises - giving you hands-on practice with delegates, lambdas, closures, and functional design patterns.