Understanding the Observer Pattern in C# with IObservable and IObserver

Leader posted 3 min read

Introduction

The Observer Pattern is a behavioral design pattern that allows an object (the subject) to notify multiple other objects (observers) about state changes. It is commonly used in event-driven programming, messaging systems, and real-time notifications.

C# provides a built-in way to implement this pattern using the IObservable and IObserver interfaces. Unlike traditional event handlers, IObservable offers better flexibility, structured error handling, and built-in subscription management.

Implementation of the Observer Pattern

The following example is based on the ObserverPattern repository by me.

using System;
using System.Collections.Generic;

namespace ObserverPattern
{
    // Observable: Order Processor
    public class OrderProcessor : IObservable<string>
    {
        private List<IObserver<string>> observers = new();

        public IDisposable Subscribe(IObserver<string> observer)
        {
            if (!observers.Contains(observer))
                observers.Add(observer);
            return new Unsubscriber(observers, observer);
        }

        public void ProcessOrder(int orderId)
        {
            Notify($"Order {orderId} is Processing");
            Notify($"Order {orderId} is Shipped");
            Notify($"Order {orderId} is Delivered");
        }

        private void Notify(string message)
        {
            foreach (var observer in observers)
                observer.OnNext(message);
        }

        private class Unsubscriber : IDisposable
        {
            private List<IObserver<string>> _observers;
            private IObserver<string> _observer;

            public Unsubscriber(List<IObserver<string>> observers, IObserver<string> observer)
            {
                _observers = observers;
                _observer = observer;
            }

            public void Dispose()
            {
                if (_observer != null && _observers.Contains(_observer))
                    _observers.Remove(_observer);
            }
        }
    }

    // Observer: Console Logger
    public class ConsoleLogger : IObserver<string>
    {
        public void OnNext(string value) => Console.WriteLine($"[Console] {value}");
        public void OnError(Exception error) => Console.WriteLine("[Console] Error occurred.");
        public void OnCompleted() => Console.WriteLine("[Console] No more updates.");
    }

    // Observer: File Logger
    public class FileLogger : IObserver<string>
    {
        private readonly string filePath = "log.txt";

        public void OnNext(string value)
        {
            using StreamWriter writer = new(filePath, append: true);
            writer.WriteLine(value);
        }

        public void OnError(Exception error)
        {
            using StreamWriter writer = new(filePath, append: true);
            writer.WriteLine("[File] Error occurred.");
        }

        public void OnCompleted()
        {
            using StreamWriter writer = new(filePath, append: true);
            writer.WriteLine("[File] No more updates.");
        }
    }

    // Main Program
    class Program
    {
        static void Main()
        {
            var orderProcessor = new OrderProcessor();
            var consoleLogger = new ConsoleLogger();
            var fileLogger = new FileLogger();

            using var consoleSubscription = orderProcessor.Subscribe(consoleLogger);
            using var fileSubscription = orderProcessor.Subscribe(fileLogger);

            orderProcessor.ProcessOrder(101);
        }
    }
}

1.OrderProcessor (Observable)

-Implements IObservable<string>    
-Notifies observers of order status changes    
-Allows observers to subscribe and unsubscribe dynamically

2.Observers (ConsoleLogger & FileLogger)

-Implement IObserver
-ConsoleLogger logs messages to the console
-FileLogger writes updates to a log file

3.Subscription Management

-Uses IDisposable to allow safe unsubscription
-Observers receive updates dynamically

Why Not Use Event Handlers (event and delegate)?

Benefits of Using IObservable and IObserver

✅ Better Resource Management: Unsubscribing is handled via IDisposable, reducing memory leaks.

✅ Built-in Error Handling: The OnError(Exception) method provides structured error handling.

✅ Completion Notification: OnCompleted() informs observers when no more updates will be sent.

✅ Supports Asynchronous & Reactive Programming: Works seamlessly with Reactive Extensions (Rx.NET).

✅ Scales Well for Complex Scenarios: Ideal for event-driven architectures where multiple observers must react to state changes dynamically.

Real-World Use Cases

  • Event-driven programming (e.g., UI events, logging systems)

  • Stock market applications (e.g., notifying traders about price

    changes)

  • Messaging and notification systems (e.g., email/SMS updates)

  • Reactive programming (e.g., Rx.NET, streaming APIs)

  • IoT (Internet of Things) (e.g., sensor data updates)

Conclusion

The Observer Pattern is an essential design pattern for event-driven applications. While event handlers (event and delegate) are useful for simple event propagation, IObservable and IObserver provide a more structured and flexible approach to managing subscriptions, error handling, and completion notifications.

By leveraging this pattern, developers can build scalable, reactive, and event-driven applications that efficiently manage state changes across multiple subscribers.

References

GitHub Repository:

        <a href="https://github.com/stevsharp/ObserverPattern" target="_blank">ObserverPattern</a>
        

Microsoft Docs:

Reactive Extensions (Rx.NET) Official:

Book Reference :

Design Patterns: Elements of Reusable Object-Oriented Software

2 Comments

0 votes
1 vote

More Posts

I’m a Senior Dev and I’ve Forgotten How to Think Without a Prompt

Karol Modelskiverified - Mar 19

TypeScript Complexity Has Finally Reached the Point of Total Absurdity

Karol Modelskiverified - Apr 23

Your Tech Stack Isn’t Your Ceiling. Your Story Is

Karol Modelskiverified - Apr 9

Tuesday Coding Tip 02 - Template with type-specific API

Jakub Neruda - Mar 10

Understanding the Producer-Consumer Pattern in C#

Spyros - Apr 28
chevron_left

Related Jobs

View all jobs →

Commenters (This Week)

4 comments
1 comment
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!