Menu
Coddy logo textTech

Observer Pattern (Events)

Part of the Object Oriented Programming section of Coddy's C# journey — lesson 54 of 70.

The Observer pattern establishes a one-to-many relationship where one object (the subject) notifies multiple dependent objects (observers) when its state changes. In C#, events provide a built-in way to implement this pattern elegantly.

The pattern involves a publisher that raises events and subscribers that respond to them. Here's a practical example of a stock price monitor:

public class Stock
{
    private decimal _price;
    
    public event Action<decimal> PriceChanged;
    
    public decimal Price
    {
        get => _price;
        set
        {
            _price = value;
            PriceChanged?.Invoke(_price);  // Notify all observers
        }
    }
}

public class PriceAlert
{
    public void OnPriceChanged(decimal newPrice)
    {
        Console.WriteLine($"Alert: Price is now {newPrice}");
    }
}

public class PriceLogger
{
    public void OnPriceChanged(decimal newPrice)
    {
        Console.WriteLine($"Logged: {newPrice}");
    }
}

Subscribers attach their methods to the event using += and can detach with -=:

var stock = new Stock();
var alert = new PriceAlert();
var logger = new PriceLogger();

stock.PriceChanged += alert.OnPriceChanged;
stock.PriceChanged += logger.OnPriceChanged;

stock.Price = 150.50m;
// Output:
// Alert: Price is now 150.50
// Logged: 150.50

stock.PriceChanged -= logger.OnPriceChanged;  // Unsubscribe
stock.Price = 155.00m;
// Output: Alert: Price is now 155.00

The Observer pattern promotes loose coupling - the Stock class doesn't need to know anything about its observers. New observers can subscribe without modifying the publisher, making the system easy to extend and maintain.

challenge icon

Challenge

Easy

Let's build a temperature monitoring system using the Observer pattern with events. You'll create a sensor that notifies multiple observers whenever the temperature changes - perfect for scenarios like climate control systems where different components need to react to temperature updates.

You'll organize your code across three files:

  • TemperatureSensor.cs: Create a TemperatureSensor class in the Monitoring namespace. This is your publisher - the subject that other objects will observe. Your sensor should have:
    • A private field to store the current temperature
    • An event called TemperatureChanged that uses Action<double> to notify subscribers of the new temperature
    • A Temperature property that raises the event whenever the temperature is set to a new value
  • Observers.cs: Create two observer classes in the same namespace that will subscribe to temperature changes:
    • DisplayUnit - has a method OnTemperatureChanged(double temp) that prints Display: {temp}C
    • AlarmSystem - has a method OnTemperatureChanged(double temp) that prints Alarm Check: {temp}C
  • Program.cs: Bring everything together by creating a sensor and both observers. Subscribe both observers to the sensor's event, then update the temperature. After that, unsubscribe the alarm system and update the temperature again to show that only the display unit receives the notification.

You will receive two inputs:

  • First temperature reading (e.g., 22.5)
  • Second temperature reading (e.g., 28.0)

Your program should:

  1. Create the sensor and both observers
  2. Subscribe both observers to the TemperatureChanged event
  3. Set the sensor's temperature to the first input value (both observers should be notified)
  4. Unsubscribe the AlarmSystem from the event
  5. Set the sensor's temperature to the second input value (only the display should be notified)

For example, if the inputs are 22.5 and 28.0, the output should be:

Display: 22.5C
Alarm Check: 22.5C
Display: 28C

This demonstrates the core power of the Observer pattern - the sensor doesn't know or care what's listening to it. Observers can subscribe and unsubscribe dynamically, and the publisher simply notifies whoever is currently listening!

Cheat sheet

The Observer pattern establishes a one-to-many relationship where one object (the subject) notifies multiple dependent objects (observers) when its state changes.

In C#, events provide a built-in way to implement this pattern. The pattern involves a publisher that raises events and subscribers that respond to them.

Basic Event Implementation

Define an event using event Action<T> and invoke it when state changes:

public class Stock
{
    private decimal _price;
    
    public event Action<decimal> PriceChanged;
    
    public decimal Price
    {
        get => _price;
        set
        {
            _price = value;
            PriceChanged?.Invoke(_price);  // Notify all observers
        }
    }
}

Creating Observers

Observers are classes with methods that match the event signature:

public class PriceAlert
{
    public void OnPriceChanged(decimal newPrice)
    {
        Console.WriteLine($"Alert: Price is now {newPrice}");
    }
}

public class PriceLogger
{
    public void OnPriceChanged(decimal newPrice)
    {
        Console.WriteLine($"Logged: {newPrice}");
    }
}

Subscribing and Unsubscribing

Use += to subscribe and -= to unsubscribe from events:

var stock = new Stock();
var alert = new PriceAlert();
var logger = new PriceLogger();

stock.PriceChanged += alert.OnPriceChanged;    // Subscribe
stock.PriceChanged += logger.OnPriceChanged;   // Subscribe

stock.Price = 150.50m;  // Both observers notified

stock.PriceChanged -= logger.OnPriceChanged;   // Unsubscribe

stock.Price = 155.00m;  // Only alert notified

The Observer pattern promotes loose coupling - the publisher doesn't need to know anything about its observers. New observers can subscribe without modifying the publisher.

Try it yourself

using System;
using Monitoring;

class Program
{
    public static void Main(string[] args)
    {
        // Read input temperatures
        double temp1 = Convert.ToDouble(Console.ReadLine());
        double temp2 = Convert.ToDouble(Console.ReadLine());

        // TODO: Create a TemperatureSensor instance

        // TODO: Create DisplayUnit and AlarmSystem instances

        // TODO: Subscribe both observers to the TemperatureChanged event

        // TODO: Set the sensor's temperature to temp1 (both observers notified)

        // TODO: Unsubscribe the AlarmSystem from the event

        // TODO: Set the sensor's temperature to temp2 (only display notified)
    }
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming