Ejemplo súper simple de observador / observable de C # con delegados

131

Recientemente comencé a profundizar en C #, pero por mi vida no puedo entender cómo funcionan los delegados al implementar el patrón observador / observable en el lenguaje.

¿Podría alguien darme un ejemplo súper simple de cómo se hace? Yo he buscado en Google este, pero todos los ejemplos que encontramos eran demasiado problema específico o demasiado "hinchada".

Deniz Dogan
fuente

Respuestas:

218

El patrón de observación generalmente se implementa con eventos .

Aquí hay un ejemplo:

using System;

class Observable
{
    public event EventHandler SomethingHappened;

    public void DoSomething() =>
        SomethingHappened?.Invoke(this, EventArgs.Empty);
}

class Observer
{
    public void HandleEvent(object sender, EventArgs args)
    {
        Console.WriteLine("Something happened to " + sender);
    }
}

class Test
{
    static void Main()
    {
        Observable observable = new Observable();
        Observer observer = new Observer();
        observable.SomethingHappened += observer.HandleEvent;

        observable.DoSomething();
    }
}

Vea el artículo vinculado para más detalles.

Tenga en cuenta que el ejemplo anterior utiliza el operador condicional nulo C # 6 para implementar de DoSomethingforma segura para manejar casos donde SomethingHappenedno se ha suscrito y, por lo tanto, es nulo. Si está utilizando una versión anterior de C #, necesitaría un código como este:

public void DoSomething()
{
    var handler = SomethingHappened;
    if (handler != null)
    {
        handler(this, EventArgs.Empty);
    }
}
Jon Skeet
fuente
17
Para ahorrar algunas líneas y evitar la verificación nula, inicialice su evento de esta manera: stackoverflow.com/questions/340610/…
Dinah
1
@Dinah: Eso no evita el cheque nulo. Todavía puede configurarlo SomethingHappened = nullmás tarde (una forma práctica, aunque floja y no ideal, de cancelar la suscripción de todos los controladores), por lo que la verificación nula siempre es necesaria.
Dan Puzey
44
@DanPuzey: Puedes dentro de la clase, pero igualmente puedes asegurarte de no hacerlo, y otro código no puede hacerlo, ya que solo puede suscribirse y darse de baja. Si se asegura de que nunca lo establece como nulo deliberadamente dentro de su clase, está bien evitar la verificación nula.
Jon Skeet
2
@ JonSkeet: por supuesto, estaba olvidando que no puedes hacer eso fuera de la clase. Disculpas!
Dan Puzey
2
Creo que puedes reemplazar todas las cosas en DoSomething conSomethingHappened?.Invoke(this, EventArgs.Empty);
Junior Mayhé
16

Aquí hay un ejemplo simple:

public class ObservableClass
{
    private Int32 _Value;

    public Int32 Value
    {
        get { return _Value; }
        set
        {
            if (_Value != value)
            {
                _Value = value;
                OnValueChanged();
            }
        }
    }

    public event EventHandler ValueChanged;

    protected void OnValueChanged()
    {
        if (ValueChanged != null)
            ValueChanged(this, EventArgs.Empty);
    }
}

public class ObserverClass
{
    public ObserverClass(ObservableClass observable)
    {
        observable.ValueChanged += TheValueChanged;
    }

    private void TheValueChanged(Object sender, EventArgs e)
    {
        Console.Out.WriteLine("Value changed to " +
            ((ObservableClass)sender).Value);
    }
}

public class Program
{
    public static void Main()
    {
        ObservableClass observable = new ObservableClass();
        ObserverClass observer = new ObserverClass(observable);
        observable.Value = 10;
    }
}

Nota:

  • Esto viola una regla en el sentido de que no desengancho al observador del observable, esto quizás sea lo suficientemente bueno para este simple ejemplo, pero asegúrese de no mantener a los observadores colgando de sus eventos de esa manera. Una forma de manejar esto sería hacer que ObserverClass IDisposable, y dejar que el método .Dispose haga lo contrario del código en el constructor
  • No se realiza ninguna comprobación de errores, al menos se debe realizar una comprobación nula en el constructor de ObserverClass
Lasse V. Karlsen
fuente
15

En este modelo, tiene editores que harán algo de lógica y publicarán un "evento".
Los editores enviarán su evento solo a los suscriptores que se hayan suscrito para recibir el evento específico.

En C #, cualquier objeto puede publicar un conjunto de eventos a los que se pueden suscribir otras aplicaciones.
Cuando la clase editorial genera un evento, se notifican todas las aplicaciones suscritas.
La siguiente figura muestra este mecanismo.

ingrese la descripción de la imagen aquí

El ejemplo más simple posible en eventos y delegados en C #:

el código se explica por sí mismo, también he agregado los comentarios para borrar el código.

  using System;

public class Publisher //main publisher class which will invoke methods of all subscriber classes
{
    public delegate void TickHandler(Publisher m, EventArgs e); //declaring a delegate
    public TickHandler Tick;     //creating an object of delegate
    public EventArgs e = null;   //set 2nd paramter empty
    public void Start()     //starting point of thread
    {
        while (true)
        {
            System.Threading.Thread.Sleep(300);
            if (Tick != null)   //check if delegate object points to any listener classes method
            {
                Tick(this, e);  //if it points i.e. not null then invoke that method!
            }
        }
    }
}

public class Subscriber1                //1st subscriber class
{
    public void Subscribe(Publisher m)  //get the object of pubisher class
    {
        m.Tick += HeardIt;              //attach listener class method to publisher class delegate object
    }
    private void HeardIt(Publisher m, EventArgs e)   //subscriber class method
    {
        System.Console.WriteLine("Heard It by Listener");
    }

}
public class Subscriber2                   //2nd subscriber class
{
    public void Subscribe2(Publisher m)    //get the object of pubisher class
    {
        m.Tick += HeardIt;               //attach listener class method to publisher class delegate object
    }
    private void HeardIt(Publisher m, EventArgs e)   //subscriber class method
    {
        System.Console.WriteLine("Heard It by Listener2");
    }

}

class Test
{
    static void Main()
    {
        Publisher m = new Publisher();      //create an object of publisher class which will later be passed on subscriber classes
        Subscriber1 l = new Subscriber1();  //create object of 1st subscriber class
        Subscriber2 l2 = new Subscriber2(); //create object of 2nd subscriber class
        l.Subscribe(m);     //we pass object of publisher class to access delegate of publisher class
        l2.Subscribe2(m);   //we pass object of publisher class to access delegate of publisher class

        m.Start();          //starting point of publisher class
    }
}

Salida:

Escuchado por el oyente

Lo escuché por Listener2

Escuchado por el oyente

Lo escuché por Listener2

Lo escuché por el oyente. . . (infinitas veces)

JerryGoyal
fuente
6

Uní algunos de los excelentes ejemplos anteriores (gracias como siempre al Sr. Skeet y al Sr. Karlsen ) para incluir un par de Observables diferentes y utilicé una interfaz para realizar un seguimiento de ellos en el Observador y permití al Observador para "observar" cualquier número de Observables a través de una lista interna:

namespace ObservablePattern
{
    using System;
    using System.Collections.Generic;

    internal static class Program
    {
        private static void Main()
        {
            var observable = new Observable();
            var anotherObservable = new AnotherObservable();

            using (IObserver observer = new Observer(observable))
            {
                observable.DoSomething();
                observer.Add(anotherObservable);
                anotherObservable.DoSomething();
            }

            Console.ReadLine();
        }
    }

    internal interface IObservable
    {
        event EventHandler SomethingHappened;
    }

    internal sealed class Observable : IObservable
    {
        public event EventHandler SomethingHappened;

        public void DoSomething()
        {
            var handler = this.SomethingHappened;

            Console.WriteLine("About to do something.");
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }
    }

    internal sealed class AnotherObservable : IObservable
    {
        public event EventHandler SomethingHappened;

        public void DoSomething()
        {
            var handler = this.SomethingHappened;

            Console.WriteLine("About to do something different.");
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }
    }

    internal interface IObserver : IDisposable
    {
        void Add(IObservable observable);

        void Remove(IObservable observable);
    }

    internal sealed class Observer : IObserver
    {
        private readonly Lazy<IList<IObservable>> observables =
            new Lazy<IList<IObservable>>(() => new List<IObservable>());

        public Observer()
        {
        }

        public Observer(IObservable observable) : this()
        {
            this.Add(observable);
        }

        public void Add(IObservable observable)
        {
            if (observable == null)
            {
                return;
            }

            lock (this.observables)
            {
                this.observables.Value.Add(observable);
                observable.SomethingHappened += HandleEvent;
            }
        }

        public void Remove(IObservable observable)
        {
            if (observable == null)
            {
                return;
            }

            lock (this.observables)
            {
                observable.SomethingHappened -= HandleEvent;
                this.observables.Value.Remove(observable);
            }
        }

        public void Dispose()
        {
            for (var i = this.observables.Value.Count - 1; i >= 0; i--)
            {
                this.Remove(this.observables.Value[i]);
            }
        }

        private static void HandleEvent(object sender, EventArgs args)
        {
            Console.WriteLine("Something happened to " + sender);
        }
    }
}
Jesse C. Slicer
fuente
Sé que esto es viejo, pero ... Esto parece seguro para subprocesos, pero no lo es. Tanto en Observer.Add como en Observer.Remove, la comprobación nula debe estar dentro de la cerradura. Desechar también debe adquirir la cerradura y establecer una bandera isDispised. De lo contrario, un buen ejemplo completo.
user5151179
5

La aplicación del patrón de observador con delegados y eventos en c # se denomina "Patrón de eventos" según MSDN, que es una ligera variación.

En este artículo encontrará ejemplos bien estructurados de cómo aplicar el patrón en c # tanto de la manera clásica como usando delegados y eventos.

Explorando el patrón de diseño del observador

public class Stock
{

    //declare a delegate for the event
    public delegate void AskPriceChangedHandler(object sender,
          AskPriceChangedEventArgs e);
    //declare the event using the delegate
    public event AskPriceChangedHandler AskPriceChanged;

    //instance variable for ask price
    object _askPrice;

    //property for ask price
    public object AskPrice
    {

        set
        {
            //set the instance variable
            _askPrice = value;

            //fire the event
            OnAskPriceChanged();
        }

    }//AskPrice property

    //method to fire event delegate with proper name
    protected void OnAskPriceChanged()
    {

        AskPriceChanged(this, new AskPriceChangedEventArgs(_askPrice));

    }//AskPriceChanged

}//Stock class

//specialized event class for the askpricechanged event
public class AskPriceChangedEventArgs : EventArgs
{

    //instance variable to store the ask price
    private object _askPrice;

    //constructor that sets askprice
    public AskPriceChangedEventArgs(object askPrice) { _askPrice = askPrice; }

    //public property for the ask price
    public object AskPrice { get { return _askPrice; } }

}//AskPriceChangedEventArgs
Anestis Kivranoglou
fuente
1
    /**********************Simple Example ***********************/    

class Program
        {
            static void Main(string[] args)
            {
                Parent p = new Parent();
            }
        }

        ////////////////////////////////////////////

        public delegate void DelegateName(string data);

        class Child
        {
            public event DelegateName delegateName;

            public void call()
            {
                delegateName("Narottam");
            }
        }

        ///////////////////////////////////////////

        class Parent
        {
            public Parent()
            {
                Child c = new Child();
                c.delegateName += new DelegateName(print);
                //or like this
                //c.delegateName += print;
                c.call();
            }

            public void print(string name)
            {
                Console.WriteLine("yes we got the name : " + name);
            }
        }
Narottam Goyal
fuente
0

No quería cambiar mi código fuente para agregar un observador adicional, así que he escrito el siguiente ejemplo simple:

//EVENT DRIVEN OBSERVER PATTERN
public class Publisher
{
    public Publisher()
    {
        var observable = new Observable();
        observable.PublishData("Hello World!");
    }
}

//Server will send data to this class's PublishData method
public class Observable
{
    public event Receive OnReceive;

    public void PublishData(string data)
    {
        //Add all the observer below
        //1st observer
        IObserver iObserver = new Observer1();
        this.OnReceive += iObserver.ReceiveData;
        //2nd observer
        IObserver iObserver2 = new Observer2();
        this.OnReceive += iObserver2.ReceiveData;

        //publish data 
        var handler = OnReceive;
        if (handler != null)
        {
            handler(data);
        }
    }
}

public interface IObserver
{
    void ReceiveData(string data);
}

//Observer example
public class Observer1 : IObserver
{
    public void ReceiveData(string data)
    {
        //sample observers does nothing with data :)
    }
}

public class Observer2 : IObserver
{
    public void ReceiveData(string data)
    {
        //sample observers does nothing with data :)
    }
}
Imran Rizvi
fuente
0

Algo como esto:

// interface implementation publisher
public delegate void eiSubjectEventHandler(eiSubject subject);

public interface eiSubject
{
    event eiSubjectEventHandler OnUpdate;

    void GenereteEventUpdate();

}

// class implementation publisher
class ecSubject : eiSubject
{
    private event eiSubjectEventHandler _OnUpdate = null;
    public event eiSubjectEventHandler OnUpdate
    {
        add
        {
            lock (this)
            {
                _OnUpdate -= value;
                _OnUpdate += value;
            }
        }
        remove { lock (this) { _OnUpdate -= value; } }
    }

    public void GenereteEventUpdate()
    {
        eiSubjectEventHandler handler = _OnUpdate;

        if (handler != null)
        {
            handler(this);
        }
    }

}

// interface implementation subscriber
public interface eiObserver
{
    void DoOnUpdate(eiSubject subject);

}

// class implementation subscriber
class ecObserver : eiObserver
{
    public virtual void DoOnUpdate(eiSubject subject)
    {
    }
}

. patrón observador C # con evento . enlace al repositorio

Elena K
fuente