Bardzo prosty przykład obserwatora C # / obserwowalnego z delegatami

134

Niedawno zacząłem zagłębiać się w C #, ale nie mogę przez całe życie dowiedzieć się, jak pracują delegaci, wdrażając obserwator / obserwowalny wzorzec w języku.

Czy ktoś mógłby mi podać super prosty przykład, jak to się robi? I nie google, ale wszystkie przykłady znalazłem były albo zbyt problemem specyficzne lub zbyt „nadęty”.

Deniz Dogan
źródło

Odpowiedzi:

222

Wzorzec obserwatora jest zwykle implementowany ze zdarzeniami .

Oto przykład:

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

Więcej szczegółów można znaleźć w powiązanym artykule.

Należy zauważyć, że powyższy przykład używa operatora warunkowego zerowego w języku C # 6 , aby zaimplementować DoSomethingbezpiecznie w celu obsługi przypadków, w których SomethingHappenednie została subskrybowana, a zatem jest null. Jeśli używasz starszej wersji C #, potrzebujesz takiego kodu:

public void DoSomething()
{
    var handler = SomethingHappened;
    if (handler != null)
    {
        handler(this, EventArgs.Empty);
    }
}
Jon Skeet
źródło
18
Aby zaoszczędzić sobie kilka wierszy i uniknąć sprawdzania zerowej wartości, zainicjuj swoje wydarzenie w ten sposób: stackoverflow.com/questions/340610/ ...
Dinah
1
@Dinah: To nie pozwala uniknąć sprawdzenia zerowego. Nadal możesz ustawić SomethingHappened = nullpóźniej (wygodny, choć leniwy i nieidealny sposób na anulowanie subskrypcji wszystkich programów obsługi), więc sprawdzanie wartości null jest zawsze konieczne.
Dan Puzey
4
@DanPuzey: Możesz w klasie, ale równie dobrze możesz się upewnić, że tego nie zrobisz - a inny kod nie może tego zrobić, ponieważ może tylko subskrybować i anulować subskrypcję. Jeśli upewnisz się, że nigdy nie ustawiłeś celowo wartości null w swojej klasie, możesz uniknąć sprawdzania wartości null.
Jon Skeet,
2
@JonSkeet: oczywiście, zapomniałem, że nie możesz tego robić poza zajęciami. Przeprosiny!
Dan Puzey
2
Myślę, że możesz zastąpić wszystkie rzeczy w DoSomethingSomethingHappened?.Invoke(this, EventArgs.Empty);
Junior Mayhé
16

Oto prosty przykład:

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

Uwaga:

  • To narusza zasadę, zgodnie z którą nie odrywam obserwatora od tego, co obserwowalne, to być może jest wystarczające dla tego prostego przykładu, ale upewnij się, że nie trzymasz obserwatorów w taki sposób. Sposobem na rozwiązanie tego problemu byłoby ustawienie ObserverClass IDisposable i pozwolenie metodzie .Dispose na wykonanie odwrotnego działania niż kod w konstruktorze
  • Nie przeprowadzono sprawdzania błędów, przynajmniej należy przeprowadzić sprawdzenie wartości null w konstruktorze klasy ObserverClass
Lasse V. Karlsen
źródło
15

W tym modelu masz wydawców, którzy zrobią logikę i opublikują „wydarzenie”.
Wydawcy będą następnie rozsyłać swoje wydarzenie tylko do subskrybentów, którzy zasubskrybowali otrzymywanie tego wydarzenia.

W języku C # każdy obiekt może publikować zestaw zdarzeń, które mogą subskrybować inne aplikacje.
Gdy klasa publikująca zgłosi zdarzenie, wszystkie subskrybowane aplikacje są powiadamiane.
Poniższy rysunek przedstawia ten mechanizm.

wprowadź opis obrazu tutaj

Najprostszy możliwy przykład dotyczący zdarzeń i delegatów w C #:

kod jest oczywisty. Dodałem również komentarze, aby wyczyścić kod.

  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
    }
}

Wynik:

Heard It by Listener

Heard It by Listener2

Heard It by Listener

Heard It by Listener2

Heard It by Listener. . . (nieskończone czasy)

GorvGoyl
źródło
6

Połączyłem ze sobą kilka wspaniałych przykładów powyżej (dziękuję jak zawsze panu Skeet i panu Karlsenowi ), aby dołączyć kilka różnych Observables i wykorzystałem interfejs do śledzenia ich w Observer i pozwoliłem Observerowi na „obserwować” dowolną liczbę Observables za pomocą wewnętrznej listy:

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
źródło
Wiem, że to jest stare, ale ... Wygląda na bezpieczne, ale tak nie jest. W obu Observer.Add i Observer.Remove sprawdzenie null musi znajdować się wewnątrz blokady. Dispose powinno również uzyskać blokadę i ustawić flagę isDispised. W przeciwnym razie dobry, kompletny przykład.
user5151179
5

Zastosowanie wzorca obserwatora z delegatami i zdarzeniami w języku C # nosi nazwę „wzorca zdarzenia” zgodnie z MSDN, co jest niewielką zmianą.

W tym artykule znajdziesz dobrze zorganizowane przykłady stosowania wzorca w języku c # zarówno w sposób klasyczny, jak i przy użyciu delegatów i zdarzeń.

Eksploracja wzorca projektowego obserwatora

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
źródło
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
źródło
0

Nie chciałem zmieniać swojego kodu źródłowego, aby dodać dodatkowego obserwatora, więc napisałem następujący prosty przykład:

//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
źródło
0

Coś takiego:

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

. wzorzec obserwatora C # ze zdarzeniem . link do repozytorium

Elena K.
źródło