Examples Of The Observer Pattern in C# - How To Simplify Event Management

Examples Of The Observer Pattern in C# - How To Simplify Event Management

Originally published at devleader.ca on November 17, 2023.

Using design patterns in software development can significantly improve efficiency and the observer pattern is one such pattern that can help efficiently manage events in C#. I feel like one of the best ways to learn concepts is through application, so we’re going to dive into some examples of the observer pattern in C#!

The observer pattern is a design pattern that allows objects to be notified of changes to their observed objects, making them aware of the changes and allowing them to act accordingly. This pattern offers many benefits, such as decoupling subjects from observers, reducing code complexity, and allowing for flexibility in updating functionality.

In this article, I’ll explain more by leveraging examples of the observer pattern in C#. I’ll also offer tips and considerations for optimizing this pattern. By the end, you’ll be a pro — or at least well enough informed to leverage the observer pattern in your next project!


Understanding the Observer Pattern in C

The observer pattern is an essential software design pattern used in event-driven programming and user interface development. It is composed of three primary elements: the subject, observer, and concrete observers. The subject class is responsible for keeping track of the observer objects and notifying them of changes in the subject’s state. On the other hand, the observer is the object that wishes to be notified when the state of the subject changes. Finally, the concrete observer is an implementation of the observer interface.

One of the observer pattern’s significant advantages is its capability to facilitate efficient event management in software development. By leveraging this ability, developers can trigger related events without the need for tightly coupling the pieces of code leading to the events. The observer pattern also ensures that the code continues to be free from changes that would cause a ripple effect or the chain reaction of changes.

Key Components of the Observer Pattern

The observer pattern’s primary components are the Subject, Observer, and Concrete Observer. The subject defines the interface for attaching and detaching observers from the subject object. The observers define the interface for receiving updates from the subject. Finally, the concrete observers maintain a reference to the subject they observe while implementing the observer interface.

Before I dive into some of the more specific practical examples of the observer pattern in C#, let’s first examine something more generic.

Implementing the Observer Pattern in C

To implement the observer pattern in C#, we follow these steps:

  • Identify the Subject to manage

  • Determine the events to publish

  • Develop a collection of Observers

  • Create event handler functions

  • Notify the observers when there is a change in a subject’s state.

And of course, the observer pattern in C# is best illustrated through a practical example. Here is a code snippet demonstrating how we can implement the observer pattern in C#:

// Declares an observer interface
public interface IObserver 
{
    void Update(string message);
}

// Declares a subject interface
public interface ISubject 
{
    void Attach(IObserver observer);
    void Detach(IObserver observer);
    void Notify(string message);
}

// Implements the observer interface
public class ConcreteObserver : IObserver 
{
    public void Update(string message) { Console.WriteLine(message); }
}

// Implements the subject interface
public class ConcreteSubject : ISubject 
{
    private List<IObserver> _observers = new List<IObserver>();

    public void Attach(IObserver observer) { _observers.Add(observer); }
    public void Detach(IObserver observer) { _observers.Remove(observer); }

    public void Notify(string message)
    {
        foreach (var observer in _observers) { observer.Update(_message); }
    }
}

To optimize the implementation, a developer should consider using a generic loosely coupled implementation of the subject interface, which will allow the code to accommodate more than one observer instance.


Advantages and Disadvantages of Using the Observer Pattern

When it comes to managing events in C#, implementing the observer pattern can have numerous benefits. One of the biggest advantages is that it can simplify the management of events, making the code easier to read and maintain. With the observer pattern, the subject and observers are decoupled, thereby increasing modularity of the code and making it easier to scale.

Another benefit of the observer pattern is that it allows for a flexible and extensible architecture. Since the observers and subjects are loosely coupled, it is easy to add or remove components, which reduces code complexity and makes it easier to test.

However, there are also some drawbacks to using the observer pattern. One of the biggest concerns is performance. The concern grows if the number of observers scales a great deal and there are many events that need to notify them all. Inefficiencies can arise from excessive allocation or complexity of how the communication is handled.

Fortunately, there are alternative patterns and approaches that can be used to manage events efficiently. One of these approaches is to use lambda or delegates, especially when the event sources are known beforehand and the events are fewer. Another approach that can be used is the Mediator pattern, which offers similar benefits to the observer pattern while reducing the number of interactions between objects and the performance overhead associated with observers. Check out this video on the observer pattern in C# for more:


Examples Of The Observer Pattern in C# — Use Cases

The observer pattern is a powerful tool that can be used to manage event-driven scenarios in C#. In this section, I’ll go over some practical scenarios where the observer pattern can be used effectively to demonstrate the pattern. I’m going to omit the implementations so that we can check that out after.

Use Case 1: Stock Market Tracker

In a stock market application, the observer pattern is ideal for tracking changes in stock prices. Different investors can subscribe to receive updates on specific stocks they are interested in.

public interface IStockObservable
{
    void Register(IStockObserver observer);
    void Unregister(IStockObserver observer);
    void NotifyStockChange(StockChangeEvent eventData);
}

public class StockChangeEvent
{
    public string StockSymbol { get; set; }
    public double NewPrice { get; set; }
}

public interface IStockObserver
{
    void Update(StockChangeEvent eventData);
}

Use Case 2: Weather Monitoring

For a weather monitoring system, various displays (like a current conditions display or a forecast display) can act as observers to a weather data subject. When the weather data changes, all the displays update their information.

public interface IWeatherObservable
{
    void Register(IWeatherObserver observer);
    void Unregister(IWeatherObserver observer);
    void NotifyWeatherChange(WeatherData eventData);
}

public class WeatherData
{
    public double Temperature { get; set; }
    public double Humidity { get; set; }
    public double Pressure { get; set; }
}

public interface IWeatherObserver
{
    void Update(WeatherData eventData);
}

Use Case 3: Online Auction System

In an online auction system, bidders can subscribe to receive updates about the bidding status of an item. Whenever a new bid is placed, the system notifies all observers (bidders) about the latest bid.

public interface IAuctionObservable
{
    void Register(IAuctionObserver observer);
    void Unregister(IAuctionObserver observer);
    void NotifyBidChange(BidChangeEvent eventData);
}

public class BidChangeEvent
{
    public string ItemName { get; set; }
    public double NewBidAmount { get; set; }
}

public interface IAuctionObserver
{
    void Update(BidChangeEvent eventData);
}

Exploring Various Implementations

In the previous section, we looked at different scenarios where you could consider the observer pattern. However, we didn’t look at examples of the observer pattern in C# where we had specific implementations. I wanted to decouple the scenarios from the implementations so we could pay special attention to the different opportunities we have.

Event and Delegate Mechanism:

One of the most simple ways to implement the observer pattern in C# is with events! Just be cautious if you try to blend this pattern with async/await.

public class MessageEventArgs : EventArgs
{
    public string Message { get; set; }
}

public sealed class MessageNotifier
{

    public event MessageReceivedHandler MessageReceived;

    public void ReceiveMessage(string message)
    {
        OnMessageReceived(new MessageEventArgs { Message = message });
    }

    private void OnMessageReceived(MessageEventArgs e)
    {
        MessageReceived?.Invoke(this, e);
    }
}

Reactive Extensions (Rx.NET):

A popular framework that you can leverage for this is Rx.NET, which you can find here on GitHub.

using System.Reactive.Subjects;

public class MessageService
{
    private readonly Subject<string> _messageStream = new Subject<string>();

    public IObservable<string> MessageStream => _messageStream.AsObservable();

    public void ReceiveMessage(string message)
    {
        _messageStream.OnNext(message);
    }
}

… And More + Other Patterns!

The point of these examples is to illustrate that you can implement a communication tech of your choosing. If you don’t need anything fancy at all, then method calls or event handlers might be your best fit. If you require more advanced communication logic in your system you can explore these other technologies mentioned.

If the observer pattern is starting to feel overly simplified for your more complex system, the mediator design pattern can be very helpful. You could even leverage message queuing systems like RabbitMQ to facilitate communication between the different parts of your system. You could also implement this pattern by leveraging the popular MediatR framework:

using MediatR;

public class NewMessageNotification : INotification
{
    public string Message { get; set; }
}

public class MessageHandler : INotificationHandler<NewMessageNotification>
{
    public Task Handle(NewMessageNotification notification, CancellationToken cancellationToken)
    {
        Console.WriteLine($"Message received: {notification.Message}");
        return Task.CompletedTask;
    }
}

General Observer Pattern Tips

To optimize the observer pattern for each use case, here are some key tips:

  • Proper event selection and delegation are important for the observer pattern to work efficiently.

  • Avoid creating too many observers because it can reduce performance.

  • Always test and optimize the observer pattern implementation to ensure it is efficient and error-free.

Overall, the observer pattern can be used in many real-world scenarios, from notification management to GUI development. Therefore, it is a crucial design pattern to have in your software engineering arsenal.


Wrapping Up Our Examples Of The Observer Pattern

The observer pattern is a powerful design pattern that can be used to efficiently manage events in C#. By implementing the observer pattern, software engineers can save valuable time and improve the readability and flexibility of their code. It is important to understand the key components of the observer pattern, how to implement it, its pros and cons, and potential use cases.

I hope the examples of the observer pattern in C# shown in this article, from scenarios to various tech choices, allow you to understand it more completely! Don’t forget to subscribe to the Dev Leader Weekly newsletter and check out my YouTube channel for more tips and guidance on C# programming!


Originally published at devleader.ca on November 17, 2023.

Want More Dev Leader Content?

  1. Follow along on this platform if you haven’t already!

  2. Subscribe to my free weekly software engineering and dotnet-focused newsletter. I include exclusive articles and early access to videos:
    SUBSCRIBE FOR FREE

  3. Watch hundreds of full-length videos on my YouTube channel:
    VISIT CHANNEL

  4. Visit my website for hundreds of articles on various software engineering topics (including code snippets):
    VISIT WEBSITE

  5. Check out the repository with many code examples from my articles and videos on GitHub:
    VIEW REPOSITORY

Did you find this article valuable?

Support Dev Leader by becoming a sponsor. Any amount is appreciated!