Exemplo super simples de observador de C # / observável com delegados

131

Recentemente, comecei a pesquisar em C #, mas na minha vida não consigo entender como os delegados funcionam ao implementar o padrão observador / observável no idioma.

Alguém poderia me dar um exemplo super simples de como isso é feito? Eu pesquisei isso, mas todos os exemplos que eu encontrei eram ou problema específico demasiado ou demasiado "inchado".

Deniz Dogan
fonte

Respostas:

218

O padrão de observador geralmente é implementado com eventos .

Aqui está um exemplo:

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();
    }
}

Veja o artigo vinculado para muito mais detalhes.

Observe que o exemplo acima usa o operador condicional nulo do C # 6 para implementar DoSomethingcom segurança para lidar com casos em SomethingHappenedque não foi inscrito e, portanto, é nulo. Se você estiver usando uma versão mais antiga do C #, precisará de um código como este:

public void DoSomething()
{
    var handler = SomethingHappened;
    if (handler != null)
    {
        handler(this, EventArgs.Empty);
    }
}
Jon Skeet
fonte
17
Para economizar algumas linhas e evitar a verificação nula, inicialize seu evento como este: stackoverflow.com/questions/340610/…
Dinah
1
@ Dinah: Isso não evita a verificação nula. Você ainda pode definir SomethingHappened = nullmais tarde (uma maneira prática, embora preguiçosa e não ideal, de cancelar a inscrição de todos os manipuladores), para que a verificação nula seja sempre necessária.
Dan Puzey
4
@ DanPuzey: Você pode fazer parte da classe, mas também pode ter certeza de que não faz isso - e outro código não pode fazê-lo, pois só pode se inscrever ou se desinscrever. Se você garantir que você nunca o definirá nulo deliberadamente na sua classe, não há problema em evitar a verificação nula.
quer
2
@ JonSkeet: é claro, eu estava esquecendo que você não pode fazer isso fora da classe. Desculpas!
Dan Puzey
2
Eu acho que você pode substituir todo o material em DoSomething comSomethingHappened?.Invoke(this, EventArgs.Empty);
júnior Mayhé
16

Aqui está um exemplo simples:

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:

  • Isso viola uma regra em que eu não solto o observador do observável, talvez seja bom o suficiente para este exemplo simples, mas certifique-se de não manter os observadores pendurados em seus eventos como esse. Uma maneira de lidar com isso seria tornar ObserverClass IDisposable e deixar o método .Dispose fazer o oposto do código no construtor
  • Nenhuma verificação de erro realizada, pelo menos uma verificação nula deve ser feita no construtor do ObserverClass
Lasse V. Karlsen
fonte
15

Nesse modelo, você tem editores que farão alguma lógica e publicarão um "evento".
Os editores enviarão o evento apenas para assinantes que se inscreveram para receber o evento específico.

Em C #, qualquer objeto pode publicar um conjunto de eventos nos quais outros aplicativos podem se inscrever.
Quando a classe de publicação gera um evento, todos os aplicativos inscritos são notificados.
A figura a seguir mostra esse mecanismo.

insira a descrição da imagem aqui

Exemplo mais simples possível em eventos e delegados em C #:

O código é auto-explicativo. Também adicionei os comentários para limpar o 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
    }
}

Resultado:

Ouvido pelo ouvinte

Heard It por Listener2

Ouvido pelo ouvinte

Heard It por Listener2

Ouvido pelo ouvinte. . . (tempos infinitos)

JerryGoyal
fonte
6

Eu juntei alguns dos grandes exemplos acima (obrigado como sempre ao Sr. Skeet e Karlsen ) por incluir alguns Observáveis ​​diferentes e usei uma interface para acompanhá-los no Observador e permiti que o Observador para "observar" qualquer número de Observáveis ​​por meio de uma 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
fonte
Eu sei que isso é antigo, mas ... Parece seguro para tópicos, mas não é. Em Observer.Add e Observer.Remove, a verificação nula precisa estar dentro do bloqueio. Descarte também deve adquirir o bloqueio e definir um sinalizador isDispised. Caso contrário, um bom exemplo completo.
user5151179
5

A aplicação do Padrão do Observador com representantes e eventos em c # é denominada "Padrão do Evento" de acordo com o MSDN, o que é uma pequena variação.

Neste artigo, você encontrará exemplos bem estruturados de como aplicar o padrão no c #, tanto da maneira clássica quanto do uso de delegados e eventos.

Explorando o padrão de design do 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
fonte
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
fonte
0

Eu não queria mudar meu código fonte para adicionar mais observadores, então escrevi o seguinte exemplo simples:

//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
fonte
0

Algo assim:

// 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)
    {
    }
}

. padrão de observador C # com evento . link para o repositório

Elena K
fonte