2009-06-01 5 views
5

Ich habe, was ich denke, ist ein einfaches "Problem", zu dem ich ein paar Lösungen gefunden habe, aber ich bin mir nicht sicher, welcher Weg zu gehen und die beste Praxis in C#.C# :: Wann verwenden Sie Ereignisse oder eine Sammlung von Objekten, die von einer Ereignisbehandlungsschnittstelle stammen?

Ich habe ein Master-Objekt (sagen ein Singleton) einmal während der Lebensdauer der Anwendung installiert. Diese "MasterClass" erzeugt eine Menge neuer Arten von Objekten, sagen wir "SlaveClass" jedes Mal, wenn MasterClass.Instance.CreateSlaveObject aufgerufen wird.

Diese MasterClass überwacht auch ein anderes Objekt auf Statusänderungen und benachrichtigt in diesem Fall die SlaveClass-Objekte, die von der Änderung erstellt wurden. Scheint einfach genug.

Da ich von der nativen C++ Welt kommen, so wie ich es tat es zuerst eine Schnittstelle

Interface IChangeEventListener 
{ 
    void ChangeHappened(); 
} 

, von denen habe ich „SlaveClass“ abgeleitet. Dann in meiner "MasterClass" ich habe:

... 
IList<IChangeEventListener> slaveList; 
... 
CreateSlaveObject 
{ 
    ... 
    slaveList.Add(slave); 
} 
... 
ChangeHappened() 
{ 
    ... 
    foreach(var slave in slaveList) 
    { 
     slave.ChangeHappened(); 
    } 
} 

Und das funktioniert. Aber ich fragte mich immer wieder, ob es eine andere (bessere) Art und Weise gibt, dies zu tun. Also recherchierte ich ein wenig mehr zum Thema und sah die C# -Ereignisse.

Anstatt also eine Sammlung von Slaves in der MasterClass zu verwalten, würde ich die MasterClass im Prinzip in den ctor von SlaveClass (oder über eine Eigenschaft) einfügen und das SlaveClass-Objekt als Event-Handler ChangeHapped hinzufügen lassen. dies würde illustriert:

...Master...   
    public delegate void ChangeHappenedDelegate(object sender, NewsInfoArgs args); 
    public event NewUpdateDelegate ChangeHappenedEvent; 
    .... 

    public SlaveClass (MasterClass publisher) //inject publisher service 
    { 
     publisher.ChangeHappenedEvent += ChangeHappened; 
    } 

Aber dies scheint wie eine unnötige Kopplung zwischen dem Slave und dem Meister zu sein, aber Ich mag die Eleganz des bereitgestellten build-in Ereignisbenachrichtigungsmechanismus.

Also sollte ich meinen aktuellen Code behalten oder zum ereignisbasierten Ansatz (mit Publisher-Injektion) wechseln? und warum?

Oder wenn Sie eine alternative Lösung vorschlagen können, die ich vielleicht verpasst habe, würde ich das auch schätzen.

Antwort

7

Nun, in meinen Augen sind Ereignisse und Schnittstellen wie du gezeigt zwei Seiten derselben Medaille (zumindest in dem Kontext, den du beschrieben hast), aber sie sind wirklich zwei Seiten davon.

Die Art, wie ich über Ereignisse nachdenke ist, dass "ich deine Veranstaltung abonnieren muss, weil ich brauche, dass du mir sagst, wenn dir etwas passiert".

Während die Schnittstelle Weg ist "muss ich eine Methode auf Sie anrufen, um Sie zu informieren, dass mir etwas passiert ist".

Es kann wie das gleiche klingen, aber es unterscheidet sich, wer spricht, in beiden Fällen ist es Ihre "Masterclass", die spricht, und das macht den Unterschied.

Wenn Ihre Slave-Klassen über eine Methode verfügen, die zum Aufruf geeignet ist, wenn in Ihrer Master-Klasse etwas passiert ist, brauchen Sie die Slave-Klasse nicht, um den Code einzubinden tut dies in Ihrer CreateSlaveClass Methode:

SlaveClass sc = new SlaveClass(); 
ChangeHappenedEvent += sc.ChangeHappened; 
return sc; 

Dies wird grundsätzlich das Event-System verwenden, aber lassen Sie den Code Masterclass alles tun, die Verdrahtung der Ereignisse.

Sind die SlaveClass-Objekte so lange wie die Singleton-Klasse? Wenn nicht, dann müssen Sie mit dem Fall umgehen, wenn sie veraltet sind oder nicht mehr benötigt werden, wie im obigen Fall (im Grunde genommen sowohl von Ihnen als auch von mir), halten Sie einen Verweis auf diese Objekte in Ihrer MasterClass, und damit auch ist niemals für die Garbage Collection geeignet, es sei denn, Sie entfernen diese Ereignisse zwangsweise oder heben die Registrierung der Schnittstellen auf.


Um das Problem mit der SlaveClass nicht so lange, wie der Meister lebt, behandeln Sie in das gleiche Kopplungsproblem laufen gehen, wie Sie auch im Kommentar zur Kenntnis genommen.

Ein Weg zu "behandeln" (beachten Sie die Anführungszeichen) dies könnte nicht direkt mit der richtigen Methode auf dem SlaveClass-Objekt verknüpfen, sondern erstellen Sie ein Wrapper-Objekt, das intern diese Methode aufrufen wird. Der Vorteil davon wäre, dass das Wrapper-Objekt intern ein WeakReference-Objekt verwenden könnte. Wenn Ihr SlaveClass-Objekt für die Garbage Collection geeignet ist, wird es möglicherweise gesammelt, und Sie versuchen dann, das nächste Mal die richtige Methode aufzurufen würde dies bemerken, und somit müssten Sie aufräumen.

Zum Beispiel wie folgt aus (und hier bin ich, ohne den Vorteil eines Visual Studio Intellisense und einem Compiler eingeben, nehmen Sie bitte die Bedeutung dieses Codes, und nicht die Syntax (Fehler).)

public class WrapperClass 
{ 
    private WeakReference _Slave; 

    public WrapperClass(SlaveClass slave) 
    { 
     _Slave = new WeakReference(slave); 
    } 

    public WrapperClass.ChangeHappened() 
    { 
     Object o = _Slave.Target; 
     if (o != null) 
      ((SlaveClass)o).ChangeHappened(); 
     else 
      MasterClass.ChangeHappenedEvent -= ChangeHappened; 
    } 
} 

In Ihrem Masterclass, würden Sie so etwas tun:

SlaveClass sc = new SlaveClass(); 
WrapperClass wc = new WrapperClass(sc); 
ChangeHappenedEvent += wc.ChangeHappened; 
return sc; 

Sobald das SlaveClass Objekt gesammelt wird, den nächsten Anruf (aber nicht früher als die) von Ihrem Masterclass zu den Event-Handler sie von der informieren ändern, all jene Wrapper, die kein Objekt mehr haben entfernt werden.

+0

Ordentlich! Ich mag den Ansatz, den Sie von dem Meister vorgeschlagen die Veranstaltung Einhakens kümmern, aber die Ereignisse Mechanismus zu halten :) Das Problem, das ich sehe, ist, wie Sie gefragt, gibt es keine Garantie, dass der Slave solange den Meister lebt ... also wenn ich das Ereignis im Slave destructor aushängen wollen, werde ich die Kupplung bringen:/ – Futurist

+0

Nun, gibt es eine Alternative, lassen Sie mich meine Antwort ändern. –

+0

Danke für die ausführliche Antwort. Ich werde darüber nachdenken, da es einige Konzepte (wie die schwache Referenz) gibt, die mir noch unbekannt sind. – Futurist

0

Ich denke, es ist nur eine Frage der persönlichen Vorliebe ... Ich persönlich mag es, Ereignisse zu verwenden, weil es besser in .NET "Philosophie" passt.

In Ihrem Fall, wenn der Masterclass ein Singleton ist, brauchen Sie nicht, dass es an den Konstruktor des SlaveClass passieren, da es mit der Singleton-Eigenschaft abgerufen werden kann (oder Methode):

public SlaveClass() 
{ 
    MasterClass.Instance.ChangeHappenedEvent += ChangeHappened; 
} 
+0

log ich nur ein wenig bit..it nicht wirklich ein Singleton ist :) Aber ich mag immer noch nicht, dass der Slave sich der Master sein, auch wenn der Master ein Singleton gewesen wäre. Danke für die Antwort trotzdem. – Futurist

0

Da Sie eine Singleton-Instanz von MasterClass zu haben scheinen, warum nicht MasterClass.Instance.ChangeHappenedEvent abonnieren? Es ist immer noch eine enge Kopplung, aber relativ ordentlich.

+0

log ich nur ein wenig bit..it ist nicht wirklich ein Singleton :) Aber Ich mag immer noch nicht, dass der Slave sich der Master sein, auch wenn der Master ein Singleton gewesen wäre. Danke für die Antwort trotzdem. – Futurist

0

Obwohl Ereignisse das normale Paradigma für die Offenlegung öffentlicher Subskribierungs-/Abmeldungsfunktionen darstellen, sind sie in vielen Fällen nicht das beste Paradigma, wenn die Funktion zum Abonnieren/Abmelden nicht verfügbar sein muss. In diesem Szenario sind die einzigen Dinge, die das Ereignis abonnieren/abbestellen können, diejenigen, die Sie selbst erstellen. Daher sind keine öffentlichen Methoden zum Abonnieren/Abmelden erforderlich. Darüber hinaus übersteigt das Wissen des Meisters über die Sklaven bei weitem das typische Wissen des Abonnenten über die Bekanntgabe von Ereignissen. Daher bevorzuge ich, dass der Master explizit die Verbindung/Trennung von Abonnements behandelt.

Eine Funktion, die ich wahrscheinlich hinzufügen würde, die durch die Kopplung zwischen Master und Slave etwas einfacher gemacht wird, wäre ein Mittel, um zu ermöglichen, dass Slaves Müll gesammelt werden, wenn alle externen Referenzen aufgegeben werden. Der einfachste Weg, dies zu tun, wäre wahrscheinlich, dass der Master eine Liste von 'WeakReference' für die Slaves führt. Wenn es notwendig ist, Sklaven darüber zu benachrichtigen, dass etwas passiert ist, gehen Sie die Liste durch, referenzieren Sie alle WeakReference, die noch am Leben ist, und benachrichtigen Sie den Slave.Wenn mehr als die Hälfte der Elemente in der Liste seit dem letzten Sweep hinzugefügt wurden und die Liste mindestens 250 Elemente enthält, kopieren Sie alle Live-Referenzen in eine neue Liste und ersetzen Sie die alte. Ein alternativer Ansatz, der es vermeiden würde, die WeakReference-Objekte so oft zu dereferenzieren, wäre, das öffentliche "Slave" -Objekt als Wrapper für ein privates Objekt zu haben und das öffentliche Objekt Override Finalize zu haben, um das private Objekt darüber zu informieren es gibt keine äußeren Hinweise darauf. Dies würde erfordern eine zusätzliche Ebene der starken Indirection für öffentliche Zugriffe auf das Objekt, aber es würde vermeiden, erstellen - auch nur vorübergehend - starke Verweise auf verlassene Objekte.

Verwandte Themen