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".
El patrón de observación generalmente se implementa con eventos .
Aquí hay un ejemplo:
using System;classObservable{publiceventEventHandlerSomethingHappened;publicvoidDoSomething()=>SomethingHappened?.Invoke(this,EventArgs.Empty);}classObserver{publicvoidHandleEvent(object sender,EventArgs args){Console.WriteLine("Something happened to "+ sender);}}classTest{staticvoidMain(){Observable observable =newObservable();Observer observer =newObserver();
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:
@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);
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
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.
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;publicclassPublisher//main publisher class which will invoke methods of all subscriber classes{publicdelegatevoidTickHandler(Publisher m,EventArgs e);//declaring a delegatepublicTickHandlerTick;//creating an object of delegatepublicEventArgs e =null;//set 2nd paramter emptypublicvoidStart()//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!}}}}publicclassSubscriber1//1st subscriber class{publicvoidSubscribe(Publisher m)//get the object of pubisher class{
m.Tick+=HeardIt;//attach listener class method to publisher class delegate object}privatevoidHeardIt(Publisher m,EventArgs e)//subscriber class method{System.Console.WriteLine("Heard It by Listener");}}publicclassSubscriber2//2nd subscriber class{publicvoidSubscribe2(Publisher m)//get the object of pubisher class{
m.Tick+=HeardIt;//attach listener class method to publisher class delegate object}privatevoidHeardIt(Publisher m,EventArgs e)//subscriber class method{System.Console.WriteLine("Heard It by Listener2");}}classTest{staticvoidMain(){Publisher m =newPublisher();//create an object of publisher class which will later be passed on subscriber classesSubscriber1 l =newSubscriber1();//create object of 1st subscriber classSubscriber2 l2 =newSubscriber2();//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}}
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;internalstaticclassProgram{privatestaticvoidMain(){var observable =newObservable();var anotherObservable =newAnotherObservable();
using (IObserver observer =newObserver(observable)){
observable.DoSomething();
observer.Add(anotherObservable);
anotherObservable.DoSomething();}Console.ReadLine();}}internalinterfaceIObservable{eventEventHandlerSomethingHappened;}internalsealedclassObservable:IObservable{publiceventEventHandlerSomethingHappened;publicvoidDoSomething(){var handler =this.SomethingHappened;Console.WriteLine("About to do something.");if(handler !=null){
handler(this,EventArgs.Empty);}}}internalsealedclassAnotherObservable:IObservable{publiceventEventHandlerSomethingHappened;publicvoidDoSomething(){var handler =this.SomethingHappened;Console.WriteLine("About to do something different.");if(handler !=null){
handler(this,EventArgs.Empty);}}}internalinterfaceIObserver:IDisposable{voidAdd(IObservable observable);voidRemove(IObservable observable);}internalsealedclassObserver:IObserver{privatereadonlyLazy<IList<IObservable>> observables =newLazy<IList<IObservable>>(()=>newList<IObservable>());publicObserver(){}publicObserver(IObservable observable):this(){this.Add(observable);}publicvoidAdd(IObservable observable){if(observable ==null){return;}lock(this.observables){this.observables.Value.Add(observable);
observable.SomethingHappened+=HandleEvent;}}publicvoidRemove(IObservable observable){if(observable ==null){return;}lock(this.observables){
observable.SomethingHappened-=HandleEvent;this.observables.Value.Remove(observable);}}publicvoidDispose(){for(var i =this.observables.Value.Count-1; i >=0; i--){this.Remove(this.observables.Value[i]);}}privatestaticvoidHandleEvent(object sender,EventArgs args){Console.WriteLine("Something happened to "+ sender);}}}
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.
publicclassStock{//declare a delegate for the eventpublicdelegatevoidAskPriceChangedHandler(object sender,AskPriceChangedEventArgs e);//declare the event using the delegatepubliceventAskPriceChangedHandlerAskPriceChanged;//instance variable for ask priceobject _askPrice;//property for ask pricepublicobjectAskPrice{set{//set the instance variable
_askPrice =value;//fire the eventOnAskPriceChanged();}}//AskPrice property//method to fire event delegate with proper nameprotectedvoidOnAskPriceChanged(){AskPriceChanged(this,newAskPriceChangedEventArgs(_askPrice));}//AskPriceChanged}//Stock class//specialized event class for the askpricechanged eventpublicclassAskPriceChangedEventArgs:EventArgs{//instance variable to store the ask priceprivateobject _askPrice;//constructor that sets askpricepublicAskPriceChangedEventArgs(object askPrice){ _askPrice = askPrice;}//public property for the ask pricepublicobjectAskPrice{get{return _askPrice;}}}//AskPriceChangedEventArgs
/**********************Simple Example ***********************/classProgram{staticvoidMain(string[] args){Parent p =newParent();}}////////////////////////////////////////////publicdelegatevoidDelegateName(string data);classChild{publiceventDelegateName delegateName;publicvoid call(){
delegateName("Narottam");}}///////////////////////////////////////////classParent{publicParent(){Child c =newChild();
c.delegateName +=newDelegateName(print);//or like this//c.delegateName += print;
c.call();}publicvoid print(string name){Console.WriteLine("yes we got the name : "+ name);}}
No quería cambiar mi código fuente para agregar un observador adicional, así que he escrito el siguiente ejemplo simple:
//EVENT DRIVEN OBSERVER PATTERNpublicclassPublisher{publicPublisher(){var observable =newObservable();
observable.PublishData("Hello World!");}}//Server will send data to this class's PublishData methodpublicclassObservable{publiceventReceiveOnReceive;publicvoidPublishData(string data){//Add all the observer below//1st observerIObserver iObserver =newObserver1();this.OnReceive+= iObserver.ReceiveData;//2nd observerIObserver iObserver2 =newObserver2();this.OnReceive+= iObserver2.ReceiveData;//publish data var handler =OnReceive;if(handler !=null){
handler(data);}}}publicinterfaceIObserver{voidReceiveData(string data);}//Observer examplepublicclassObserver1:IObserver{publicvoidReceiveData(string data){//sample observers does nothing with data :)}}publicclassObserver2:IObserver{publicvoidReceiveData(string data){//sample observers does nothing with data :)}}
SomethingHappened = null
má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.SomethingHappened?.Invoke(this, EventArgs.Empty);
Aquí hay un ejemplo simple:
Nota:
fuente
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.
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.
Salida:
Escuchado por el oyente
Lo escuché por Listener2
Escuchado por el oyente
Lo escuché por Listener2
Lo escuché por el oyente. . . (infinitas veces)
fuente
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:
fuente
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
fuente
fuente
No quería cambiar mi código fuente para agregar un observador adicional, así que he escrito el siguiente ejemplo simple:
fuente
Algo como esto:
. patrón observador C # con evento . enlace al repositorio
fuente