Back
C# Observer Design Pattern: A Complete Guide | Code & Examples

C# Observer Design Pattern: A Complete Guide | Code & Examples

The Observer pattern is a behavioral design pattern that defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

Key Components

  1. Subject (Observable): Maintains a list of observers and notifies them of state changes
  2. Observer: Interface that defines the update method for objects that should be notified
  3. Concrete Observers: Implement the Observer interface and react to notifications

Example Scenario: Stock Market Monitoring

Let's implement a stock market system where multiple displays need to show updated stock prices when they change.

Step 1: Define the Observer Interface

public interface IObserver
{
    void Update(string stockSymbol, decimal stockPrice);
}

Step 2: Create the Subject (Observable) Interface and Implementation

public interface ISubject
{
    void RegisterObserver(IObserver observer);
    void RemoveObserver(IObserver observer);
    void NotifyObservers();
}

public class StockMarket : ISubject
{
    private List<IObserver> _observers = new List<IObserver>();
    private Dictionary<string, decimal> _stockPrices = new Dictionary<string, decimal>();

    public void RegisterObserver(IObserver observer)
    {
        _observers.Add(observer);
    }

    public void RemoveObserver(IObserver observer)
    {
        _observers.Remove(observer);
    }

    public void NotifyObservers()
    {
        foreach (var stock in _stockPrices)
        {
            foreach (var observer in _observers)
            {
                observer.Update(stock.Key, stock.Value);
            }
        }
    }

    public void UpdateStockPrice(string stockSymbol, decimal newPrice)
    {
        if (_stockPrices.ContainsKey(stockSymbol))
        {
            _stockPrices[stockSymbol] = newPrice;
        }
        else
        {
            _stockPrices.Add(stockSymbol, newPrice);
        }
        NotifyObservers();
    }
}

Step 3: Implement Concrete Observers

public class StockPriceDisplay : IObserver
{
    public void Update(string stockSymbol, decimal stockPrice)
    {
        Console.WriteLine($"Stock Display: {stockSymbol} is now ${stockPrice}");
    }
}

public class MobileStockApp : IObserver
{
    public void Update(string stockSymbol, decimal stockPrice)
    {
        Console.WriteLine($"Mobile App Notification: {stockSymbol} price update - ${stockPrice}");
    }
}

public class StockAnalyzer : IObserver
{
    private Dictionary<string, decimal> _priceHistory = new Dictionary<string, decimal>();

    public void Update(string stockSymbol, decimal stockPrice)
    {
        if (!_priceHistory.ContainsKey(stockSymbol))
        {
            _priceHistory.Add(stockSymbol, stockPrice);
            Console.WriteLine($"Analyzer: First price recorded for {stockSymbol}");
        }
        else
        {
            decimal change = stockPrice - _priceHistory[stockSymbol];
            Console.WriteLine($"Analyzer: {stockSymbol} changed by {change:C} ({change/_priceHistory[stockSymbol]:P})");
            _priceHistory[stockSymbol] = stockPrice;
        }
    }
}

Step 4: Using the Observer Pattern

class Program
{
    static void Main(string[] args)
    {
        // Create the subject (observable)
        StockMarket market = new StockMarket();

        // Create observers
        StockPriceDisplay display = new StockPriceDisplay();
        MobileStockApp mobileApp = new MobileStockApp();
        StockAnalyzer analyzer = new StockAnalyzer();

        // Register observers
        market.RegisterObserver(display);
        market.RegisterObserver(mobileApp);
        market.RegisterObserver(analyzer);

        // Update stock prices (this will notify all observers)
        market.UpdateStockPrice("AAPL", 182.63m);
        market.UpdateStockPrice("MSFT", 403.78m);
        
        Console.WriteLine("\nRemoving mobile app observer...\n");
        market.RemoveObserver(mobileApp);
        
        market.UpdateStockPrice("AAPL", 183.25m);
        market.UpdateStockPrice("GOOGL", 173.45m);
    }
}

Example Output

Stock Display: AAPL is now $182.63 Mobile App Notification: AAPL price update - $182.63 Analyzer: First price recorded for AAPL Stock Display: MSFT is now $403.78 Mobile App Notification: MSFT price update - $403.78 Analyzer: First price recorded for MSFT Removing mobile app observer... Stock Display: AAPL is now $183.25 Analyzer: AAPL changed by $0.62 (0.34 %) Stock Display: GOOGL is now $173.45 Analyzer: First price recorded for GOOGL

Advantages of Observer Pattern

  1. Loose coupling: Subjects know nothing about observers except that they implement the Observer interface
  2. Dynamic relationships: Observers can be added or removed at runtime
  3. Broadcast communication: A single notification goes to multiple observers

Real-World Usage in .NET

The .NET framework uses the Observer pattern in:

  • Event handling system (events/delegates)
  • INotifyPropertyChanged interface (used in WPF/MVVM)
  • IObservable<T> and IObserver<T> interfaces in System namespace

Alternative Implementation using Events

In C#, you can also implement the Observer pattern using events:

public class StockMarket
{
    public event Action<string, decimal> StockPriceChanged;

    public void UpdateStockPrice(string stockSymbol, decimal newPrice)
    {
        StockPriceChanged?.Invoke(stockSymbol, newPrice);
    }
}

// Usage:
StockMarket market = new StockMarket();
market.StockPriceChanged += (symbol, price) => 
    Console.WriteLine($"Stock {symbol} is now {price}");

This event-based approach is more idiomatic in C# for many observer scenarios.

Comments - Beta - WIP

Leave a Comment