AKTUALISIEREN
Ab C # 6 lautet die Antwort auf diese Frage:
SomeEvent?.Invoke(this, e);
Ich höre / lese häufig den folgenden Rat:
Erstellen Sie immer eine Kopie eines Ereignisses, bevor Sie es überprüfen null
und auslösen. Dadurch wird ein potenzielles Problem beim Threading beseitigt, bei dem das Ereignis null
genau zwischen dem Ort, an dem Sie auf Null prüfen, und dem Ort, an dem Sie das Ereignis auslösen, auftritt:
// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;
if (copy != null)
copy(this, EventArgs.Empty); // Call any handlers on the copied list
Aktualisiert : Ich habe beim Lesen über Optimierungen gedacht, dass das Ereignismitglied möglicherweise auch volatil sein muss, aber Jon Skeet gibt in seiner Antwort an, dass die CLR die Kopie nicht optimiert.
Damit dieses Problem überhaupt auftritt, muss ein anderer Thread Folgendes getan haben:
// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;
// Good, now we can be certain that OnTheEvent will not run...
Die tatsächliche Reihenfolge könnte diese Mischung sein:
// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;
// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;
// Good, now we can be certain that OnTheEvent will not run...
if (copy != null)
copy(this, EventArgs.Empty); // Call any handlers on the copied list
Der Punkt ist, dass OnTheEvent
läuft, nachdem der Autor sich abgemeldet hat, und sie sich nur speziell abgemeldet haben, um dies zu vermeiden. Sicherlich ist eine benutzerdefinierte Ereignisimplementierung mit entsprechender Synchronisation in den add
und remove
Accessoren wirklich erforderlich . Darüber hinaus besteht das Problem möglicher Deadlocks, wenn eine Sperre gehalten wird, während ein Ereignis ausgelöst wird.
Also ist das Cargo Cult Programming ? Es scheint so - viele Leute müssen diesen Schritt unternehmen, um ihren Code vor mehreren Threads zu schützen, obwohl Ereignisse in Wirklichkeit meiner Meinung nach viel mehr Sorgfalt erfordern, bevor sie als Teil eines Multithread-Designs verwendet werden können . Folglich können Personen, die diese zusätzliche Sorgfalt nicht anwenden, diesen Rat genauso gut ignorieren - es ist einfach kein Problem für Single-Thread-Programme, und angesichts des Fehlens des volatile
meisten Online-Beispielcodes kann der Ratschlag keine enthalten Wirkung überhaupt.
(Und ist es nicht viel einfacher, nur das Leerzeichen delegate { }
in der Mitgliedererklärung zuzuweisen, damit Sie überhaupt nicht danach suchen müssen null
?)
Aktualisiert:Falls es nicht klar war, habe ich die Absicht des Ratschlags verstanden - unter allen Umständen eine Nullreferenzausnahme zu vermeiden. Mein Punkt ist, dass diese spezielle Nullreferenzausnahme nur auftreten kann, wenn ein anderer Thread aus dem Ereignis entfernt wird. Der einzige Grund dafür besteht darin, sicherzustellen, dass keine weiteren Anrufe über dieses Ereignis empfangen werden, was mit dieser Technik eindeutig NICHT erreicht wird . Sie würden eine Rennbedingung verbergen - es wäre besser, sie zu enthüllen! Diese Null-Ausnahme hilft, einen Missbrauch Ihrer Komponente zu erkennen. Wenn Sie möchten, dass Ihre Komponente vor Missbrauch geschützt wird, können Sie dem Beispiel von WPF folgen. Speichern Sie die Thread-ID in Ihrem Konstruktor und lösen Sie eine Ausnahme aus, wenn ein anderer Thread versucht, direkt mit Ihrer Komponente zu interagieren. Oder implementieren Sie eine wirklich threadsichere Komponente (keine leichte Aufgabe).
Ich behaupte also, dass das bloße Ausführen dieser Kopier- / Überprüfungssprache eine Frachtkultprogrammierung ist, die Ihrem Code Chaos und Rauschen hinzufügt. Um sich tatsächlich vor anderen Threads zu schützen, ist viel mehr Arbeit erforderlich.
Update als Antwort auf die Blog-Beiträge von Eric Lippert:
Es gibt also eine wichtige Sache, die ich an Event-Handlern vermisst habe: "Event-Handler müssen robust sein, wenn sie auch nach dem Abbestellen des Events aufgerufen werden", und deshalb müssen wir uns natürlich nur um die Möglichkeit des Events kümmern delegieren sein null
. Ist diese Anforderung an Event-Handler irgendwo dokumentiert?
Und so: "Es gibt andere Möglichkeiten, um dieses Problem zu lösen. Zum Beispiel das Initialisieren des Handlers mit einer leeren Aktion, die niemals entfernt wird. Eine Nullprüfung ist jedoch das Standardmuster."
Das einzige verbleibende Fragment meiner Frage ist also, warum das "Standardmuster" explizit auf Null überprüft wird. Die Alternative, den leeren Delegierten zuzuweisen, muss nur = delegate {}
zur Ereigniserklärung hinzugefügt werden, und dies beseitigt diese kleinen Haufen stinkender Zeremonien an jedem Ort, an dem das Ereignis ausgelöst wird. Es wäre einfach sicherzustellen, dass der leere Delegat billig zu instanziieren ist. Oder fehlt mir noch etwas?
Sicherlich muss es sein, dass (wie Jon Skeet vorgeschlagen hat) dies nur ein .NET 1.x-Rat ist, der nicht ausgestorben ist, wie es 2005 hätte tun sollen?
EventName(arguments)
, den Delegaten des Ereignisses bedingungslos aufzurufen, anstatt den Delegaten nur dann aufzurufen, wenn er nicht null ist (nichts tun, wenn null).