Observer Pattern
Notify registered receivers of a change without making the source depend on every receiver.
· Last updated: 2026-10-24 · 5 min read
A temperature sensor feeds a display. Later an alarm also needs its readings. If the sensor directly calls Display.Show() and HeatAlarm.Check(), every new receiver means changing the sensor’s code.
Instead, let a receiver register the method it wants called. The sensor keeps those registrations and announces a reading through them. It needs to know how to call a registered method, not whether the receiver is a screen, an alarm, or something added later.
Observer separates a source of change from the receivers registered to hear about it. The four authors of Design Patterns, known as the GoF, classify it as a behavioral pattern. The source is often called a subject, and the receivers are observers.1
Subscriptions in C#
C# events provide a compact way to express this within one process. EventHandler<TemperatureChanged> describes a method that receives the sender and a TemperatureChanged value. Changed is the event to which those methods subscribe.2
The reading is a separate object so every receiver gets the same Celsius value without fetching the sensor’s mutable state again. Display shows it; HeatAlarm decides whether it reaches the alarm threshold.
A small executable example
using System;
using System.Globalization;
var sensor = new TemperatureSensor();
var display = new Display();
var alarm = new HeatAlarm();
sensor.Changed += display.Show;
sensor.Changed += alarm.Check;
sensor.Measure(28m);
sensor.Measure(31m);
sensor.Changed -= alarm.Check;
sensor.Measure(32m);
public sealed class TemperatureChanged : EventArgs
{
public decimal Celsius { get; }
public TemperatureChanged(decimal celsius) => Celsius = celsius;
}
public sealed class TemperatureSensor
{
public event EventHandler<TemperatureChanged>? Changed;
public void Measure(decimal celsius) => Changed?.Invoke(this, new TemperatureChanged(celsius));
}
public sealed class Display
{
public void Show(object? sender, TemperatureChanged change) =>
Console.WriteLine("Display: " + change.Celsius.ToString(CultureInfo.InvariantCulture));
}
public sealed class HeatAlarm
{
public void Check(object? sender, TemperatureChanged change)
{
if (change.Celsius >= 30m)
Console.WriteLine("Alarm: " + change.Celsius.ToString(CultureInfo.InvariantCulture));
}
}Display: 28
Display: 31
Alarm: 31
Display: 32The += lines subscribe the display and alarm methods. At 28 degrees both methods are invoked, but the alarm prints nothing because its threshold is 30. At 31 degrees both print. The -= line removes the alarm subscription, so only the display receives the 32-degree reading.
Changed?.Invoke(...) means that the sensor invokes the event only when it has subscribers. An unsubscribed sensor can still accept a measurement. The sensor class contains neither a Display field nor a HeatAlarm field.
The notification sequence
The sequence diagram leaves out the first, 28-degree measurement to keep the two later readings visible. Time runs downward. The alarm disappears from the notification path after its subscription is removed.
Who removes the subscription
The publisher holds references to subscribed handlers. A screen that subscribes should unsubscribe when it no longer needs readings, or the publisher can keep that screen alive. Use a named handler, or retain the delegate (the callable reference you registered), when you will need to remove the subscription later.3
This example invokes handlers synchronously in registration order. A slow handler delays the measurement call; an exception from one handler stops the remaining invocations and reaches the caller. Applications that need isolation or asynchronous delivery must define that behavior explicitly. Registering the same handler twice also creates two subscriptions.
Events and message delivery
These notifications live in this process’s memory. For another process to receive them, or for delivery to survive a restart, a transport and storage policy are needed as well. Observer alone does not provide durable message delivery. With one fixed receiver, a direct method call can be simpler.
Related reading
The complete example runs as a .NET 10 console program.
Footnotes
- Gamma, Helm, Johnson and Vlissides. Design Patterns, Observer. Publisher and catalog. ↩
- Microsoft. Events. ↩
- Microsoft. Subscribe to and unsubscribe from events. ↩