Skip to Content
Course content

107: The Observer Pattern with Events

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've spent years reviewing code for junior and mid-level devs, and I consistently see the same stumbling block when it comes to the Observer pattern. Most people approach it as a "Design Pattern" they have to implement from scratch—meaning they spend an hour writing custom IObserver and IObservable interfaces, managing internal lists of subscribers, and writing manual foreach loops to notify every object in that list.

Thinking You Need Manual Interface Boilerplate for Every Observer

When you first read the Gang of Four definition of the Observer pattern, it feels like you need a rigid architecture. You might write something like this:

// The "Textbook" way (Too much work in C#)
public interface IPriceObserver {
    void Update(decimal price);
}

public class StockTicker {
    private List<IPriceObserver> _observers = new();
    public void Attach(IPriceObserver observer) => _observers.Add(observer);
    public void Notify(decimal price) {
        foreach(var obs in _observers) obs.Update(price);
    }
}

This works, but it's clunky. You're forcing every "observer" class to implement a specific interface, which creates tight coupling. If you want to observe a StockTicker and a WeatherStation in the same class, you're suddenly implementing five different interfaces. It's a maintenance nightmare.

Leveraging C# Events as a Built-in Observer Implementation

Here is the secret: C# has the Observer pattern baked directly into the language via delegates and events. An event is essentially a managed list of function pointers. When you trigger an event, the CLR handles the "loop and notify" logic for you. You don't need interfaces; you just need a method signature that matches.

Let's look at how we'd handle a stock price update the "C# way." We'll use EventHandler<T>, which is the standard pattern for this.

public class PriceChangedEventArgs : EventArgs {
    public decimal NewPrice { get; }
    public PriceChangedEventArgs(decimal price) => NewPrice = price;
}

public class StockTicker {
    // This is our "Subject" in Observer terms
    public event EventHandler<PriceChangedEventArgs> PriceChanged;

    public void UpdatePrice(decimal price) {
        // The ?. ensures we don't crash if there are no subscribers
        PriceChanged?.Invoke(this, new PriceChangedEventArgs(price));
    }
}

Now, any class can "observe" this ticker without implementing a single interface. They just subscribe using the += operator. I love this approach because the StockTicker doesn't need to know anything about who is listening—it just shouts into the void, and whoever cares responds.

public class TradingBot {
    public void OnPriceChanged(object sender, PriceChangedEventArgs e) {
        if (e.NewPrice < 150.00m) {
            Console.WriteLine("Buying the dip!");
        }
    }
}

// Wiring it up
var ticker = new StockTicker();
var bot = new TradingBot();

ticker.PriceChanged += bot.OnPriceChanged; // Subscription
ticker.UpdatePrice(145.00m); // Triggers the bot

One professional tip: Always remember that events create a strong reference from the publisher to the subscriber. If your StockTicker lives for the entire duration of the app, but your TradingBot is meant to be temporary, the bot will never be garbage collected because the ticker is still holding onto it. Get in the habit of unsubscribing with -= when the observer is no longer needed. It's a small detail that separates the seniors from the juniors.




📋 Practical Task

Implementing a Smart Home Environmental Alert System

Your task is to build a small system where a TemperatureSensor notifies different parts of a house when the temperature changes. Instead of the sensor knowing about the hardware it controls, it should use the Observer pattern (via events) to broadcast changes.

  • Create a TemperatureChangedEventArgs class that inherits from EventArgs and holds a double CurrentTemp property.
  • Create a TemperatureSensor class that has an event EventHandler<TemperatureChangedEventArgs> TempChanged. Include a method SetTemperature(double temp) that triggers the event.
  • Create two separate observer classes:
    • AirConditioner: Should print "AC Turning On" if the temperature exceeds 25.0°C.
    • HomeLogger: Should print "Log: Temperature updated to [temp]" regardless of the value.
  • In a Main method, instantiate the sensor and both observers, subscribe them to the event, and simulate a temperature rise from 20.0°C to 30.0°C to verify both observers react correctly.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.