Benutzerdefinierte Ereignisse und Ereignisaccessoren in Komponenten für Windows-Runtime
Die .NET-Unterstützung für Windows-Runtime-Komponenten erleichtert das Deklarieren von Ereigniskomponenten, indem die Unterschiede zwischen dem Ereignismuster Universelle Windows-Plattform (UWP) und dem .NET-Ereignismuster ausgeblendet werden. Wenn Sie jedoch benutzerdefinierte Ereignisaccessoren in einer Windows-Runtime Komponente deklarieren, müssen Sie dem Muster folgen, das in der UWP verwendet wird.
Registrieren von Ereignissen
Wenn Sie sich für die Behandlung eines Ereignisses in der UWP registrieren, gibt der Add-Accessor ein Token zurück. Um die Registrierung aufzuheben, übergeben Sie dieses Token an den Remove-Accessor. Dies bedeutet, dass das Hinzufügen und Entfernen von Accessoren für UWP-Ereignisse unterschiedliche Signaturen von den Accessoren aufweist, für die Sie verwendet werden.
Glücklicherweise vereinfachen die Visual Basic- und C#-Compiler diesen Prozess: Wenn Sie ein Ereignis mit benutzerdefinierten Accessoren in einer Windows-Runtime Komponente deklarieren, verwenden die Compiler automatisch das UWP-Muster. Sie erhalten beispielsweise einen Compilerfehler, wenn ihr Add-Accessor kein Token zurückgibt. .NET bietet zwei Typen zur Unterstützung der Implementierung:
- Die EventRegistrationToken-Struktur stellt das Token dar.
- Die EventRegistrationTokenTable<T-Klasse> erstellt Token und verwaltet eine Zuordnung zwischen Token und Ereignishandlern. Das generische Typargument ist der Ereignisargumenttyp. Sie erstellen eine Instanz dieser Klasse für jedes Ereignis, wenn ein Ereignishandler zum ersten Mal für dieses Ereignis registriert wird.
Der folgende Code für das NumberChanged-Ereignis zeigt das grundlegende Muster für UWP-Ereignisse. In diesem Beispiel verwendet der Konstruktor für das Ereignisargumentobjekt NumberChangedEventArgs einen einzelnen ganzzahligen Parameter, der den geänderten numerischen Wert darstellt.
Beachten Sie, dass dies dasselbe Muster ist, das von den Compilern für gewöhnliche Ereignisse verwendet wird, die Sie in einer Windows-Runtime Komponente deklarieren.
private EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
m_NumberChangedTokenTable = null;
public event EventHandler<NumberChangedEventArgs> NumberChanged
{
add
{
return EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
.GetOrCreateEventRegistrationTokenTable(ref m_NumberChangedTokenTable)
.AddEventHandler(value);
}
remove
{
EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
.GetOrCreateEventRegistrationTokenTable(ref m_NumberChangedTokenTable)
.RemoveEventHandler(value);
}
}
internal void OnNumberChanged(int newValue)
{
EventHandler<NumberChangedEventArgs> temp =
EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
.GetOrCreateEventRegistrationTokenTable(ref m_NumberChangedTokenTable)
.InvocationList;
if (temp != null)
{
temp(this, new NumberChangedEventArgs(newValue));
}
}
Private m_NumberChangedTokenTable As _
EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs))
Public Custom Event NumberChanged As EventHandler(Of NumberChangedEventArgs)
AddHandler(ByVal handler As EventHandler(Of NumberChangedEventArgs))
Return EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs)).
GetOrCreateEventRegistrationTokenTable(m_NumberChangedTokenTable).
AddEventHandler(handler)
End AddHandler
RemoveHandler(ByVal token As EventRegistrationToken)
EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs)).
GetOrCreateEventRegistrationTokenTable(m_NumberChangedTokenTable).
RemoveEventHandler(token)
End RemoveHandler
RaiseEvent(ByVal sender As Class1, ByVal args As NumberChangedEventArgs)
Dim temp As EventHandler(Of NumberChangedEventArgs) = _
EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs)).
GetOrCreateEventRegistrationTokenTable(m_NumberChangedTokenTable).
InvocationList
If temp IsNot Nothing Then
temp(sender, args)
End If
End RaiseEvent
End Event
Die statische (in Visual Basic freigegebene) GetOrCreateEventRegistrationTokenTable-Methode erstellt die Instanz des Ereignisses des EventRegistrationTokenTable<T-Objekts> lazily. Übergeben Sie das Feld auf Klassenebene, das die Tokentabelleninstanz an diese Methode enthält. Wenn das Feld leer ist, erstellt die Methode die Tabelle, speichert einen Verweis auf die Tabelle im Feld und gibt einen Verweis auf die Tabelle zurück. Wenn das Feld bereits einen Tokentabellenverweis enthält, gibt die Methode nur diesen Verweis zurück.
Wichtig , um die Threadsicherheit sicherzustellen, muss das Feld, das die Instanz des Ereignisses von EventRegistrationTokenTable<T> enthält, ein Feld auf Klassenebene sein. Wenn es sich um ein Feld auf Klassenebene handelt, stellt die GetOrCreateEventRegistrationTokenTable-Methode sicher, dass alle Threads, wenn mehrere Threads versuchen, die Tokentabelle zu erstellen, dieselbe Instanz der Tabelle erhalten. Für ein bestimmtes Ereignis müssen alle Aufrufe der GetOrCreateEventRegistrationTokenTable-Methode dasselbe Feld auf Klassenebene verwenden.
Durch Aufrufen der GetOrCreateEventRegistrationTokenTable-Methode im Remove-Accessor und in der RaiseEvent-Methode (die OnRaiseEvent-Methode in C#) wird sichergestellt, dass keine Ausnahmen auftreten, wenn diese Methoden aufgerufen werden, bevor Ereignishandlerdelegat hinzugefügt wurden.
Die anderen Member der EventRegistrationTokenTable<T-Klasse> , die im UWP-Ereignismuster verwendet werden, umfassen Folgendes:
Die AddEventHandler-Methode generiert ein Token für den Ereignishandlerdelegat, speichert den Delegaten in der Tabelle, fügt es der Aufrufliste hinzu und gibt das Token zurück.
Die RemoveEventHandler(EventRegistrationToken) -Methodenüberladung entfernt den Delegaten aus der Tabelle und aus der Aufrufliste.
Beachten Sie, dass die Methoden AddEventHandler und RemoveEventHandler(EventRegistrationToken) die Tabelle sperren, um die Threadsicherheit zu gewährleisten.
Die InvocationList - Eigenschaft gibt einen Delegaten zurück, der alle Ereignishandler enthält, die derzeit für die Behandlung des Ereignisses registriert sind. Verwenden Sie diesen Delegaten, um das Ereignis auszuheben, oder verwenden Sie die Methoden der Delegate-Klasse, um die Handler einzeln aufzurufen.
Beachten Sie , dass Sie dem muster folgen, das weiter oben in diesem Artikel gezeigt wurde, und kopieren Sie die Stellvertretung auf eine temporäre Variable, bevor Sie sie aufrufen. Dadurch wird eine Racebedingung vermieden, in der ein Thread den letzten Handler entfernt, wodurch der Delegat auf NULL reduziert wird, unmittelbar bevor ein anderer Thread versucht, den Delegaten aufzurufen. Stellvertretungen sind unveränderlich, sodass die Kopie weiterhin gültig ist.
Platzieren Sie Ihren eigenen Code entsprechend in den Accessoren. Wenn Threadsicherheit ein Problem darstellt, müssen Sie ihre eigene Sperrung für Ihren Code bereitstellen.
C#-Benutzer: Wenn Sie benutzerdefinierte Ereignisaccessoren im UWP-Ereignismuster schreiben, stellt der Compiler nicht die üblichen syntaktischen Verknüpfungen bereit. Es generiert Fehler, wenn Sie den Namen des Ereignisses in Ihrem Code verwenden.
Visual Basic-Benutzer: In .NET ist ein Ereignis nur ein Multicastdelegat, der alle registrierten Ereignishandler darstellt. Das Auslösen des Ereignisses bedeutet nur das Aufrufen des Delegaten. In der Regel blendet die Visual Basic-Syntax die Interaktionen mit dem Delegaten aus, und der Compiler kopiert den Delegaten, bevor er aufgerufen wird, wie in der Notiz zur Threadsicherheit beschrieben. Wenn Sie ein benutzerdefiniertes Ereignis in einer Windows-Runtime Komponente erstellen, müssen Sie sich direkt mit der Stellvertretung befassen. Dies bedeutet auch, dass Sie beispielsweise die MulticastDelegate.GetInvocationList-Methode verwenden können, um ein Array abzurufen, das einen separaten Delegaten für jeden Ereignishandler enthält, wenn Sie die Handler separat aufrufen möchten.