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.00The 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
EasyLet'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 aTemperatureSensorclass in theMonitoringnamespace. 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
TemperatureChangedthat usesAction<double>to notify subscribers of the new temperature - A
Temperatureproperty 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 methodOnTemperatureChanged(double temp)that printsDisplay: {temp}CAlarmSystem- has a methodOnTemperatureChanged(double temp)that printsAlarm 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:
- Create the sensor and both observers
- Subscribe both observers to the
TemperatureChangedevent - Set the sensor's temperature to the first input value (both observers should be notified)
- Unsubscribe the
AlarmSystemfrom the event - 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: 28CThis 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 notifiedThe 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)
}
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesNamespaces & DirectivesIntro to Classes & ObjectsThe 'this' KeywordMethods and ParametersFields vs PropertiesConstructorsObject InitializersRecap - Simple Calculator4Inheritance
Basic Inheritance (:) SyntaxThe 'base' KeywordVirtual & Override KeywordsSealed ClassesThe 'object' Base ClassRecap - Employee Hierarchy7Advanced Features
Operator OverloadingIndexers (this[])ToString() OverrideExtension MethodsRecap - Custom List10Design Patterns Part 1
Intro to Design PatternsThread-Safe SingletonFactory PatternObserver Pattern (Events)Strategy Pattern2Properties & Static Members
Auto-Implemented PropertiesRead/Write-Only PropertiesStatic Fields & MethodsStatic ClassesExpression-Bodied Members5Polymorphism & Interfaces
Compile vs Runtime PolyInterface vs Abstract ClassMultiple InterfacesExplicit InterfacesUpcasting & DowncastingRecap - Shape Calculator