Exemple super simple d'observateur / observable C # avec des délégués

131

J'ai récemment commencé à creuser dans C # mais je ne peux pas de ma vie comprendre comment les délégués fonctionnent lors de l'implémentation du modèle observable / observable dans le langage.

Quelqu'un pourrait-il me donner un exemple très simple de la façon dont cela se fait? J'ai googlé, mais tous les exemples que je trouvais étaient trop de problèmes spécifiques ou trop « pléthorique ».

Deniz Dogan
la source

Réponses:

218

Le modèle d'observateur est généralement implémenté avec des événements .

Voici un exemple:

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

Voir l'article lié pour plus de détails.

Notez que l'exemple ci-dessus utilise l' opérateur conditionnel null C # 6 pour implémenter en DoSomethingtoute sécurité pour gérer les cas où SomethingHappenedn'a pas été abonné et est donc null. Si vous utilisez une ancienne version de C #, vous aurez besoin d'un code comme celui-ci:

public void DoSomething()
{
    var handler = SomethingHappened;
    if (handler != null)
    {
        handler(this, EventArgs.Empty);
    }
}
Jon Skeet
la source
17
Pour vous épargner quelques lignes et éviter le contrôle nul, initialisez votre événement comme ceci: stackoverflow.com/questions/340610/...
Dinah
1
@Dinah: Cela n'évite pas la vérification de null. Vous pouvez toujours définir SomethingHappened = nullplus tard (un moyen pratique, bien que paresseux et non idéal de désabonner tous les gestionnaires), de sorte que la vérification de null est toujours nécessaire.
Dan Puzey
4
@DanPuzey: Vous pouvez dans la classe, mais vous pouvez également vous assurer de ne pas le faire - et d' autres codes ne peuvent pas le faire, car il ne peut que s'abonner et se désabonner. Si vous vous assurez de ne jamais le définir délibérément sur null dans votre classe, évitez la vérification de null.
Jon Skeet
2
@JonSkeet: bien sûr, j'oubliais que vous ne pouvez pas faire ça en dehors de la classe. Toutes mes excuses!
Dan Puzey
2
Je pense que vous pouvez remplacer tout le SomethingHappened?.Invoke(this, EventArgs.Empty);
contenu de
16

Voici un exemple 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;
    }
}

Remarque:

  • Cela viole une règle en ce sens que je ne décroche pas l'observateur de l'observable, c'est peut-être suffisant pour cet exemple simple, mais assurez-vous de ne pas laisser les observateurs suspendus à vos événements comme ça. Un moyen de gérer cela serait de rendre ObserverClass IDisposable et de laisser la méthode .Dispose faire le contraire du code dans le constructeur
  • Aucune vérification d'erreur effectuée, au moins une vérification nulle doit être effectuée dans le constructeur de l'ObserverClass
Lasse V. Karlsen
la source
15

Dans ce modèle, vous avez des éditeurs qui feront un peu de logique et publieront un "événement".
Les éditeurs enverront ensuite leur événement uniquement aux abonnés qui se sont abonnés pour recevoir l'événement spécifique.

En C #, n'importe quel objet peut publier un ensemble d'événements auxquels d'autres applications peuvent s'abonner.
Lorsque la classe de publication déclenche un événement, toutes les applications souscrites sont notifiées.
La figure suivante montre ce mécanisme.

entrez la description de l'image ici

Exemple le plus simple possible sur les événements et les délégués en C #:

le code est explicite, j'ai également ajouté les commentaires pour effacer le code.

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

Production:

Entendu par l'auditeur

Entendu par Auditeur2

Entendu par l'auditeur

Entendu par Auditeur2

Je l'ai entendu par l'auditeur. . . (temps infini)

JerryGoyal
la source
6

J'ai lié quelques-uns des excellents exemples ci-dessus (merci comme toujours à M. Skeet et M. Karlsen ) pour inclure quelques observables différents et j'ai utilisé une interface pour les suivre dans l'observateur et permis à l'observateur de pour "observer" n'importe quel nombre d'Observables via une liste interne:

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
la source
Je sais que c'est vieux, mais ... Cela semble sans danger pour les threads, mais ce n'est pas le cas. Dans Observer.Add et Observer.Remove, la vérification de null doit être à l'intérieur du verrou. Dispose doit également acquérir le verrou et définir un indicateur isDispised. Sinon, un bon exemple complet.
user5151179
5

L'application du modèle d'observateur avec des délégués et des événements dans c # s'appelle «modèle d'événement» selon MSDN qui est une légère variation.

Dans cet article, vous trouverez des exemples bien structurés de la façon d'appliquer le modèle en c # à la fois de manière classique et en utilisant des délégués et des événements.

Explorer le modèle de conception d'observateur

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
la source
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
la source
0

Je ne voulais pas changer mon code source pour ajouter un observateur supplémentaire, j'ai donc écrit l'exemple simple suivant:

//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
la source
0

Quelque chose comme ça:

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

. modèle d'observateur C # avec événement . lien vers le référentiel

Elena K
la source