c# – How to organize the interaction of an object with its observer

Question:

Let's say I have a class A. In class A there is a set of events that reflect any changes inside – changes in collections, changes in basic properties / state. And the object has a property – an object of class B, which must monitor these states and perform some work by analyzing the set of all changes.

It is logical that when creating an object of class B, pass it to the constructor a reference to an object of class A and bind to events.

I am interested in any other methods, implementations or patterns.

Answer:

I'm interested in any other ways, implementations or patterns

In C #, the observer pattern can be implemented in many ways, for example:

  • Delegate based
  • Event based
  • Using a strongly typed interface
  • Using special interfaces IObserver/IObservable

Delegate based

The delegate solution is a classic callback. The advantage of this method is the absence of an explicit, typed event handler (the relationship between the provider and the consumer of the event is not regulated in any way) and the simplicity of the method – when an event occurs, the method of the Consumer class calls the delegate argument:

class Consumer
{
    public void Event(Action callback)
    {   
        ...

        callback();
    }
}

When an event occurs, the act delegate and its entire invocation list will be called, thus supporting multiple event handling.

Event based

The method is similar to the previous one, but with the difference that it allows you to organize subscription to events with any number of handlers (without modeling a 1: 1 ratio as in the previous method) or not have handlers at all.

class Consumer
{
    public event EventHandler<EventArgs> Event;

    void RaiseEvent()
    { 
        // Не самый потокобезопасный код
        if (Event != null) {
            Event.Invoke(this, new EventArgs());
        }
    }
}

public class Program
{
    public static void Main()
    {
        Consumer consumer = new Consumer();

        consumer.Event += (object sender, EventArgs args) => { /*  */ };
        consumer.Event += (object sender, EventArgs args) => { /*  */ };
    }
}

The method is similar to the previous one, but does not guarantee the presence of at least one handler.

Using a strongly typed interface

In this method, the consumer handles the events of the provider using a specific handler interface, strictly formalizing the relationship between the provider and the consumer.

interface IEventHandler
{
    void FirstEventHandler();
    void SecondEventHandler();
}

class Supplier : IEventHandler
{
    public void FirstEventHandler()
    {
        /* Обработчик события */
    }

    public void SecondEventHandler()
    {
        /* Обработчик другого события */
    }
}

class Consumer
{
    IEventHandler _supplier;

    public void Subscribe(IEventHandler supplier)
    {
       _supplier = supplier;
    }

    void RaiseFirstEvent()
    {
        _supplier.FirstEventHandler();
    }

    void RaiseSecondEvent()
    {
        _supplier.SecondEventHandler();
    }
}

public class Program
{
    public static void Main()
    {
        Consumer consumer = new Consumer();
        Supplier supplier = new Supplier(); 

        consumer.Subscribe(supplier);
    }
}

This is almost a classic implementation of the subscriber pattern in C # (and the delegate pattern in Objective-C, with the only difference that all handlers must be strictly defined).

Implementing this method requires close monitoring for SRP violations and vendor event consistency.

Using special interfaces IObserver / IObservable

Since .NET 4, special IObserver/IObservable interfaces are available to implement the observer pattern for event sequences :

class Data {

}

class Consumer : IObserver<Data>
{
   public void OnCompleted()
   {

   }

   public void OnError(Exception e)
   {

   }

   public void OnNext(Data data)
   {
       // Обработка новых данных 
   }
}

class Supplier : IObservable<Data>
{
    List<IObserver<Data>> _subscribers = new List<IObserver<Data>>();

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

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

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

    public IDisposable Subscribe(IObserver<Data> observer) 
    {
        _subscribers.Add(observer);

        return new Unsubscriber(_subscribers, observer);
    }

    void RaiseEvent()
    {
        foreach (var subscriber in _subscribers) {
            subscriber.OnNext(new Data());
        }
    }
}

public class Program
{
    public static void Main()
    {
        Consumer consumer = new Consumer();
        Supplier supplier = new Supplier();

        supplier.Subscribe(consumer);
    }
}
Scroll to Top