Dofactory.com
Dofactory.com

C# Observer Design Pattern

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

C# code examples of the Observer design pattern is provided in 3 forms:

Frequency of use:
high
C# Design Patterns

UML class diagram

A visualization of the classes and objects participating in this pattern.

Participants

The classes and objects participating in this pattern include:

  • Subject  (Stock)
    • knows its observers. Any number of Observer objects may observe a subject
    • provides an interface for attaching and detaching Observer objects.
  • ConcreteSubject  (IBM)
    • stores state of interest to ConcreteObserver
    • sends a notification to its observers when its state changes
  • Observer  (IInvestor)
    • defines an updating interface for objects that should be notified of changes in a subject.
  • ConcreteObserver  (Investor)
    • maintains a reference to a ConcreteSubject object
    • stores state that should stay consistent with the subject's
    • implements the Observer updating interface to keep its state consistent with the subject's

Structural code in C#

This structural code demonstrates the Observer pattern in which registered objects are notified of and updated with a state change.

using System;
using System.Collections.Generic;

namespace Observer.Structural
{
    /// <summary>
    /// Observer Design Pattern
    /// </summary>

    public class Program
    {
        public static void Main(string[] args)
        {
            // Configure Observer pattern

            ConcreteSubject s = new ConcreteSubject();

            s.Attach(new ConcreteObserver(s, "X"));
            s.Attach(new ConcreteObserver(s, "Y"));
            s.Attach(new ConcreteObserver(s, "Z"));

            // Change subject and notify observers

            s.SubjectState = "ABC";
            s.Notify();

            // Wait for user

            Console.ReadKey();
        }
    }

    /// <summary>
    /// The 'Subject' abstract class
    /// </summary>

    public abstract class Subject
    {
        private List<Observer> observers = new List<Observer>();

        public void Attach(Observer observer)
        {
            observers.Add(observer);
        }

        public void Detach(Observer observer)
        {
            observers.Remove(observer);
        }

        public void Notify()
        {
            foreach (Observer o in observers)
            {
                o.Update();
            }
        }
    }

    /// <summary>
    /// The 'ConcreteSubject' class
    /// </summary>

    public class ConcreteSubject : Subject
    {
        private string subjectState;

        // Gets or sets subject state

        public string SubjectState
        {
            get { return subjectState; }
            set { subjectState = value; }
        }
    }

    /// <summary>
    /// The 'Observer' abstract class
    /// </summary>

    public abstract class Observer
    {
        public abstract void Update();
    }

    /// <summary>
    /// The 'ConcreteObserver' class
    /// </summary>

    public class ConcreteObserver : Observer
    {
        private string name;
        private string observerState;
        private ConcreteSubject subject;

        // Constructor

        public ConcreteObserver(
            ConcreteSubject subject, string name)
        {
            this.subject = subject;
            this.name = name;
        }

        public override void Update()
        {
            observerState = subject.SubjectState;
            Console.WriteLine("Observer {0}'s new state is {1}",
                name, observerState);
        }

        // Gets or sets subject

        public ConcreteSubject Subject
        {
            get { return subject; }
            set { subject = value; }
        }
    }
}
Output
Observer X's new state is ABC
Observer Y's new state is ABC
Observer Z's new state is ABC

Real-world code in C#

This real-world code demonstrates the Observer pattern in which registered investors are notified every time a stock changes value.

using System;
using System.Collections.Generic;

namespace Observer.RealWorld
{
    /// <summary>
    /// Observer Design Pattern
    /// </summary>

    public class Program
    {
        public static void Main(string[] args)
        {
            // Create IBM stock and attach investors

            IBM ibm = new IBM("IBM", 120.00);
            ibm.Attach(new Investor("Sorros"));
            ibm.Attach(new Investor("Berkshire"));

            // Fluctuating prices will notify investors

            ibm.Price = 120.10;
            ibm.Price = 121.00;
            ibm.Price = 120.50;
            ibm.Price = 120.75;

            // Wait for user

            Console.ReadKey();
        }
    }

    /// <summary>
    /// The 'Subject' abstract class
    /// </summary>

    public abstract class Stock
    {
        private string symbol;
        private double price;
        private List<IInvestor> investors = new List<IInvestor>();

        // Constructor

        public Stock(string symbol, double price)
        {
            this.symbol = symbol;
            this.price = price;
        }

        public void Attach(IInvestor investor)
        {
            investors.Add(investor);
        }

        public void Detach(IInvestor investor)
        {
            investors.Remove(investor);
        }

        public void Notify()
        {
            foreach (IInvestor investor in investors)
            {
                investor.Update(this);
            }

            Console.WriteLine("");
        }

        // Gets or sets the price

        public double Price
        {
            get { return price; }
            set
            {
                if (price != value)
                {
                    price = value;
                    Notify();
                }
            }
        }

        // Gets the symbol

        public string Symbol
        {
            get { return symbol; }
        }
    }

    /// <summary>
    /// The 'ConcreteSubject' class
    /// </summary>

    public class IBM : Stock
    {
        // Constructor

        public IBM(string symbol, double price)
            : base(symbol, price)
        {
        }
    }

    /// <summary>
    /// The 'Observer' interface
    /// </summary>

    public interface IInvestor
    {
        void Update(Stock stock);
    }

    /// <summary>
    /// The 'ConcreteObserver' class
    /// </summary>

    public class Investor : IInvestor
    {
        private string name;
        private Stock stock;

        // Constructor

        public Investor(string name)
        {
            this.name = name;
        }

        public void Update(Stock stock)
        {
            Console.WriteLine("Notified {0} of {1}'s " +
                "change to {2:C}", name, stock.Symbol, stock.Price);
        }

        // Gets or sets the stock

        public Stock Stock
        {
            get { return stock; }
            set { stock = value; }
        }
    }
}
Output
Notified Sorros of IBM's change to $120.10
Notified Berkshire of IBM's change to $120.10

Notified Sorros of IBM's change to $121.00
Notified Berkshire of IBM's change to $121.00

Notified Sorros of IBM's change to $120.50
Notified Berkshire of IBM's change to $120.50

Notified Sorros of IBM's change to $120.75
Notified Berkshire of IBM's change to $120.75

.NET Optimized code in C#

The .NET optimized code demonstrates the same code as above but uses more modern C# and .NET features.

Here is an elegant C# Observer solution.

namespace Observer.NetOptimized;

using static System.Console;
using System;

/// <summary>
/// Observer Design Pattern
/// </summary>
public class Program
{
    public static void Main()
    {
        // Create IBM stock and attach investors
        var ibm = new IBM(120.00);

        // Attach 'listeners', i.e. Investors
        ibm.Attach(new Investor { Name = "Sorros" });
        ibm.Attach(new Investor { Name = "Berkshire" });

        // Fluctuating prices will notify listening investors
        ibm.Price = 120.10;
        ibm.Price = 121.00;
        ibm.Price = 120.50;
        ibm.Price = 120.75;

        // Wait for user
        ReadKey();
    }
}

// Custom event arguments
public class ChangeEventArgs : EventArgs
{
    // Gets or sets symbol
    public string Symbol { get; set; }

    // Gets or sets price
    public double Price { get; set; }
}

/// <summary>
/// The 'Subject' abstract class
/// </summary>
public abstract class Stock(string symbol, double price)
{
    protected string symbol = symbol;
    protected double price = price;

    // Event
    public event EventHandler<ChangeEventArgs> Change = null!;

    // Invoke the Change event
    public virtual void OnChange(ChangeEventArgs e)
    {
        Change?.Invoke(this, e);
    }

    public void Attach(IInvestor investor)
    {
        Change += investor.Update;
    }

    public void Detach(IInvestor investor)
    {
        Change -= investor.Update;
    }

    // Gets or sets the price
    public double Price
    {
        get => price; 
        set
        {
            if (price != value)
            {
                price = value;
                OnChange(new ChangeEventArgs { Symbol = symbol, Price = price });
                WriteLine("");
            }
        }
    }
}

/// <summary>
/// The 'ConcreteSubject' class
/// </summary>
public class IBM(double price) : Stock("IBM", price)
{
}

/// <summary>
/// The 'Observer' interface
/// </summary>
public interface IInvestor
{
    void Update(object sender, ChangeEventArgs e);
}

/// <summary>
/// The 'ConcreteObserver' class
/// </summary>
public class Investor : IInvestor
{
    // Gets or sets the investor name
    public string Name { get; set; } = null!;

    // Gets or sets the stock
    public Stock Stock { get; set; } = null!;

    public void Update(object sender, ChangeEventArgs e)
    {
        WriteLine("Notified {0} of {1}'s " +
            "change to {2:C}", Name, e.Symbol, e.Price);
    }
}
Output
Notified Sorros of IBM's change to $120.10
Notified Berkshire of IBM's change to $120.10

Notified Sorros of IBM's change to $121.00
Notified Berkshire of IBM's change to $121.00

Notified Sorros of IBM's change to $120.50
Notified Berkshire of IBM's change to $120.50

Notified Sorros of IBM's change to $120.75
Notified Berkshire of IBM's change to $120.75



Last updated on Mar 17, 2024

Want to know more?


Learn how to build .NET applications in 33 days with design patterns, ultra clean architecture, and more.

Learn more about our Dofactory .NET developer package.


Guides


vsn 3.2