2009-06-18 3 views
39

Ich habe eine generische EreignishandlerC#: Ereignis mit Explizitheit hinzufügen/entfernen! = Typisches Ereignis?

erklärt
public delegate void EventHandler(); 

auf die ich die Erweiterungsmethode ‚Raise‘ hinzugefügt haben:

public static void RaiseEvent(this EventHandler self)  { 
    if (self != null) self.Invoke(); 
} 

Wenn definiere ich das Ereignis die typische Syntax

public event EventHandler TypicalEvent; 
Verwendung

dann kann ich die Erweiterungsmethode ohne Probleme aufrufen:

TypicalEvent.RaiseEvent(); 

Aber wenn ich das Ereignis mit expliziten hinzufügen/entfernen Syntax definieren

private EventHandler _explicitEvent; 
public event EventHandler ExplicitEvent { 
    add { _explicitEvent += value; } 
    remove { _explicitEvent -= value; } 
} 

dann der Extension-Methode existiert nicht auf dem Fall mit expliziter Add definiert/entfernen Syntax:

ExplicitEvent.RaiseEvent(); //RaiseEvent() does not exist on the event for some reason 

Und Wenn ich zum Ereignis rüber schwinge, um den Grund zu sehen, heißt es:

Das Ereignis 'ExplicitEvent' kann nur erscheinen auf der linken Seite von + = oder - =

Warum sollte ein Ereignis die typische Syntax von einem Ereignis unterschiedlich sein definiert mit definierten die explizite hinzufügen/entfernen Syntax und warum Erweiterungsmethoden funktionieren nicht auf letzterem?

EDIT: Ich fand es kann direkt mit den privaten Event-Handler um es:

_explicitEvent.RaiseEvent(); 

Aber ich verstehe immer noch nicht, warum ich nicht das Ereignis direkt wie das Ereignis verwenden kann definiert mit die typische Syntax. Vielleicht kann mich jemand aufklären.

+0

Sie können die Erweiterungsmethode auch nicht außerhalb der Klasse verwenden. – IllidanS4

Antwort

34

Da Sie dies tun können (es ist nicht-reale Probe, aber es „funktioniert“):

private EventHandler _explicitEvent_A; 
private EventHandler _explicitEvent_B; 
private bool flag; 
public event EventHandler ExplicitEvent { 
    add { 
     if (flag = !flag) { _explicitEvent_A += value; /* or do anything else */ } 
     else { _explicitEvent_B += value; /* or do anything else */ } 
    } 
    remove { 
     if (flag = !flag) { _explicitEvent_A -= value; /* or do anything else */ } 
     else { _explicitEvent_B -= value; /* or do anything else */ } 
    } 
} 

Wie kann der Compiler wissen, was es sollte tun „ExplicitEvent.RaiseEvent();“ ? Antwort: Es kann nicht.

Die "ExplicitEvent.RaiseEvent();" ist nur die Syntax Zucker, die nur dann prädiziert werden kann, wenn das Ereignis implizit implementiert ist.

+7

Woher wissen Sie, dass der Compiler ein "He" ist? Nur Spaß, danke für die Antwort und die Probe, die es wirklich sehr gut erklärt. Ich liebe diese Seite für blitzschnelle Antworten wie diese. –

+0

+1 die blitzschnelle Sache. Das hat mich zurückkommen lassen. Das und ... die immense Menge an Wissen. –

1

Die "einfache" Deklaration für TypicalEvent führt einige Compiler-Tricks aus. Es erstellt einen Ereignis-Metadateneintrag, fügt Methoden hinzu und entfernt sie und ein Unterstützungsfeld. Wenn Ihr Code auf TypicalEvent verweist, übersetzt der Compiler es in eine Referenz auf das Hintergrundfeld; Wenn sich externer Code auf "TypicalEvent" bezieht (mit + = und - =), übersetzt der Compiler den Code in einen Verweis auf die add- oder remove-Methode.

Die "explizite" Deklaration umgeht diese Compiler-Tricks. Sie schreiben die Methoden add und remove und das Feld backing: tatsächlich, wie TcKs darauf hinweist, darf nicht einmal ein Hintergrundfeld sein (dies ist ein häufiger Grund für die Verwendung des expliziten Formulars: siehe zB Ereignisse in System.Windows .Forms.Control).Daher kann der Compiler nicht übersetzen länger ruhig die Referenz in einem Verweis auf das dahinter liegende Feld TypicalEvent: Wenn Sie das dahinter liegende Feld möchten, die tatsächliche Delegatobjekt, können Sie das dahinter liegende Feld direkt zu verweisen haben:

_explicitEvent.RaiseEvent() 
+2

Nicht ganz - innerhalb der Klasse bezieht sich + = und - = wird * noch * auf das Feld. Es bezieht sich auf das Ereignis, wenn Sie von außerhalb der Klasse darauf verweisen. –

+0

Danke für Ihre Antwort. Du hast es sehr gut erklärt. Ich habe die Problemumgehung bereits selbst gefunden, aber trotzdem danke für das Beispiel, wie ich RaisEvent() für das explizite Ereignis aufrufen würde. –

+0

Jon, danke für die Einsicht. Es ist immer gut, sich an die Feinheiten der Verwendung von Ereignissen zu erinnern. –

49

Wenn Sie erstellen Sie eine "Feld-like" Ereignis, wie folgt aus:

public event EventHandler Foo; 

der Compiler erzeugt ein Feld und ein Ereignis. Innerhalb des Quellcodes der Klasse, die das Ereignis deklariert, versteht der Compiler bei jeder Bezugnahme auf Foo, dass Sie sich auf das Feld beziehen. Das Feld ist jedoch privat. Wenn Sie sich also auf Foo von anderen Klassen beziehen, verweist es auf das Ereignis (und daher den Code hinzufügen/entfernen).

Wenn Sie Ihren eigenen expliziten Code zum Hinzufügen/Entfernen deklarieren, erhalten Sie kein automatisch generiertes Feld. Sie haben also nur ein Ereignis und können ein Ereignis nicht direkt in C# auslösen - Sie können nur eine Delegateninstanz aufrufen. Ein Ereignis ist keine Delegat-Instanz, es ist nur ein Add/Remove-Paar.

Nun, Ihr Code diese enthalten:

public EventHandler TypicalEvent; 

Das noch etwas anders - es war kein Ereignis überhaupt erklärt wird - es wurde EventHandler ein öffentliches Feld des Delegattyp erklärt. Jeder kann das aufrufen, da der Wert nur eine Delegat-Instanz ist. Es ist wichtig, den Unterschied zwischen einem Feld und einem Ereignis zu verstehen. Sie sollten diese Art von Code niemals schreiben, genauso wie ich sicher bin, dass Sie normalerweise keine öffentlichen Felder anderer Typen wie string und int haben. Leider ist es ein leichter Tippfehler und ein relativ schwerer zu stoppen. Sie würden es nur bemerken, wenn Sie bemerken, dass der Compiler es Ihnen erlaubte, den Wert einer anderen Klasse zuzuweisen oder zu verwenden.

Siehe meine article on events and delegates für weitere Informationen.

+0

Danke Jon, ich habe deinen Artikel schon vor einiger Zeit gelesen, es war mir einfach nicht klar, als ich dieses "seltsame Verhalten" (zu dieser Zeit) mit der Erweiterungsmethode bemerkte. –

+0

Also Ereignis verhält sich wie Eigentum. – Alex78191

+0

@ Alex78191: Nun, eine Art - außer mit add/remove statt get/set. –

7

Das liegt daran, dass Sie nicht richtig sehen. Die Logik ist dieselbe wie in Eigenschaften. Sobald Sie das Hinzufügen/Entfernen festgelegt haben, ist es kein tatsächliches Ereignis mehr, sondern ein Wrapper, um das tatsächliche Ereignis zu zeigen (Ereignisse können nur innerhalb der Klasse selbst ausgelöst werden, sodass Sie immer Zugriff auf das reale Ereignis haben).

private EventHandler _explicitEvent; 
public event EventHandler ExplicitEvent { 
    add { _explicitEvent += value; } 
    remove { _explicitEvent -= value; } 
} 

private double seconds; 
public double Hours 
{ 
    get { return seconds/3600; } 
    set { seconds = value * 3600; } 
} 

In beiden Fällen enthält das Element mit der Eigenschaft get/set oder add/remove keine Daten. Sie benötigen ein "echtes" privates Mitglied, das die eigentlichen Daten enthält. Mit den Eigenschaften können Sie zusätzliche Logik programmieren, wenn Sie die Mitglieder der Außenwelt aussetzen.

Ein gutes Beispiel dafür, warum Sie es tun möchten, ist, zusätzliche Berechnungen zu stoppen, wenn sie nicht benötigt werden (niemand hört auf das Ereignis).

Zum Beispiel können sagen, die Ereignisse durch einen Timer ausgelöst werden, und wir wollen nicht, den Timer zu arbeiten, wenn niemand auf das Ereignis registriert ist:

private System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer(); 
private EventHandler _explicitEvent; 
public event EventHandler ExplicitEvent 
{ 
    add 
    { 
     if (_explicitEvent == null) timer.Start(); 
     _explicitEvent += value; 
    } 
    remove 
    { 
     _explicitEvent -= value; 
     if (_explicitEvent == null) timer.Stop(); 
    } 
} 

Sie wahrscheinlich würde wollen Sperren Sie das Hinzufügen/Entfernen mit einem Objekt (ein nachträglicher Einfall) ...