Delegaten (C++/CX)
Das delegate
Schlüsselwort wird verwendet, um einen Verweistyp zu deklarieren, der das Windows-Runtime Äquivalent eines Funktionsobjekts in C++ ist. Eine Delegatdeklaration ähnlich einer Funktionssignatur; sie gibt den Rückgabetyp und die Parametertypen an, über die die umschlossene Funktion verfügen muss. Dies ist eine benutzerdefinierte Delegatdeklaration:
public delegate void PrimeFoundHandler(int result);
Delegaten werden am häufigsten in Verbindung mit Ereignissen verwendet. Ein Ereignis hat einen Delegattyp, ähnlich wie eine Klasse einen Schnittstellentyp haben kann. Der Delegat stellt einen Vertrag dar, den Ereignishandler erfüllen müssen. Hier ist ein Ereignisklassenmitglied, dessen Typ der zuvor definierten Stellvertretung ist:
event PrimeFoundHandler^ primeFoundEvent;
Verwenden Sie beim Deklarieren von Delegaten, die clients über die Windows-Runtime Anwendungs-Binärschnittstelle verfügbar gemacht werden, Windows::Foundation::TypedEventHandler<TSender, TResult>. Dieser Delegat besitzt vordefinierte Proxy- und Stubbinärdateien, die es ermöglichen, dass er von JavaScript-Clients verwendet wird.
Verwenden von Delegaten
Wenn Sie eine Universelle Windows-Plattform-App erstellen, arbeiten Sie häufig mit einem Delegat als Typ eines Ereignisses, das eine Windows-Runtime Klasse verfügbar macht. Um ein Ereignis zu abonnieren, erstellen Sie eine Instanz des Delegattyps durch Angabe einer Funktion (oder Lambda), die mit der Delegatsignatur übereinstimmt. Verwenden Sie dann den +=
-Operator, um das Delegatobjekt an den Ereignismember in der Klasse zu übergeben. Dies wird als Abonnieren des Ereignisses bezeichnet. Wenn die Klasseninstanz das Ereignis auslöst, wird Ihre Funktion aufgerufen, zusammen mit allen anderen Handlern, die von Ihrem Objekt oder anderen Objekten hinzugefügt wurden.
Tipp
Visual Studio übernimmt viel Arbeit für Sie, wenn Sie einen Ereignishandler erstellen. Wenn Sie beispielsweise einen Ereignishandler in XAML-Markup angeben, wird eine QuickInfo angezeigt. Wenn Sie die QuickInfo auswählen, erstellt Visual Studio automatisch die Ereignishandlermethode und ordnet sie dem Ereignis in der Veröffentlichungsklasse zu.
Das grundlegende Muster wird im folgenden Beispiel veranschaulicht. Windows::Foundation::TypedEventHandler
ist der Delegattyp. Die Handlerfunktion wird erstellt, indem eine benannte Funktion verwendet wird.
In app.h:
[Windows::Foundation::Metadata::WebHostHiddenAttribute]
ref class App sealed
{
void InitializeSensor();
void SensorReadingEventHandler(Windows::Devices::Sensors::LightSensor^ sender,
Windows::Devices::Sensors::LightSensorReadingChangedEventArgs^ args);
float m_oldReading;
Windows::Devices::Sensors::LightSensor^ m_sensor;
};
In app.cpp:
void App::InitializeSensor()
{
// using namespace Windows::Devices::Sensors;
// using namespace Windows::Foundation;
m_sensor = LightSensor::GetDefault();
// Create the event handler delegate and add
// it to the object's event handler list.
m_sensor->ReadingChanged += ref new TypedEventHandler<LightSensor^,
LightSensorReadingChangedEventArgs^>( this,
&App::SensorReadingEventHandler);
}
void App::SensorReadingEventHandler(LightSensor^ sender,
LightSensorReadingChangedEventArgs^ args)
{
LightSensorReading^ reading = args->Reading;
if (reading->IlluminanceInLux > m_oldReading)
{/*...*/}
}
Warnung
Im Allgemeinen empfiehlt es sich bei einem Ereignishandler, eine benannte Funktion statt eines Lambda-Ausdrucks zu verwenden. Andernfalls müssen Sie sorgfältig darauf achten, Zirkelverweise zu vermeiden. Eine benannte Funktion übernimmt den this-Zeiger als schwachen Verweis, ein Lambda den this-Zeiger allerdings als starken Verweis und erstellt einen Zirkelverweis. Weitere Informationen finden Sie unter "Schwache Verweise" und "Unterbrechungszyklen".
In der Konvention weisen Ereignishandlerdelegatnamen, die durch die Windows-Runtime definiert sind, das Formular *EventHandler auf, z. B. RoutedEventHandler, SizeChangedEventHandler oder SuspendingEventHandler. Außerdem haben Ereignishandlerdelegaten zwei Parameter und geben "ungültig" zurück. In einem Delegaten, der keine Typparameter aufweist, ist der erste Parameter vom Typ Platform::Object Class^; er enthält einen Verweis auf den Absender, also das Objekt, das das Ereignis ausgelöst hat. Sie müssen es wieder in den ursprünglichen Typ umwandeln, bevor Sie das Argument in der Ereignishandlermethode verwenden. In einem Eventhandlerdelegaten, der über Typparameter verfügt, gibt der erste Typparameter den Typ des Absenders an und der zweite Parameter ist ein Handle auf eine Verweisklasse, die Informationen zum Ereignis enthält. In der Konvention heißt diese Klasse *EventArgs. Beispielsweise verfügt ein RoutedEventHandler-Delegat über einen zweiten Parameter des Typs RoutedEventArgs^ und DragEventHander verfügt über einen zweiten Parameter des Typs DragEventArgs^.
Gemäß der Konvention werden Delegaten, die den Code umschließen, der ausgeführt wird, wenn ein asynchroner Vorgang abgeschlossen ist, *CompletedHandler benannt. Diese Delegaten werden als Eigenschaften in der Klasse, nicht als Ereignisse definiert. Verwenden Sie deshalb nicht den +=
-Operator, um sie zu abonnieren, sondern weisen der Eigenschaft nur ein Delegatobjekt zu.
Tipp
C++ IntelliSense zeigt nicht die vollständige Signatur des Delegaten an; hilft Ihnen also nicht, den konkreten Typ des EventArgs-Parameters zu bestimmen. Um den Typ zu finden, können Sie zum Objektkatalog wechseln und sich die Invoke
-Methode für den Delegaten ansehen.
Erstellen von benutzerdefinierten Delegaten
Sie können ihre eigenen Delegaten definieren, Ereignishandler definieren oder Es Consumern ermöglichen, benutzerdefinierte Funktionen an Ihre Windows-Runtime Komponente zu übergeben. Wie jeder andere Windows-Runtime Typ kann eine öffentliche Stellvertretung nicht als generisch deklariert werden.
Deklaration
Die Deklaration eines Delegaten ähnelt einer Funktionsdeklaration. Der Delegat ist hierbei jedoch ein Typ. In der Regel deklarieren Sie einen Delegaten im Namespace-Gültigkeitsbereich, Sie können jedoch auch eine Delegatdeklaration innerhalb einer Klassendeklaration schachteln. Der folgende Delegat kapselt jede Funktion, die eine ContactInfo^
als Eingabe akzeptiert und einen Platform::String^
zurückgibt.
public delegate Platform::String^ CustomStringDelegate(ContactInfo^ ci);
Nachdem Sie einen Delegattyp deklariert haben, können Sie Klassenmember dieses Typs deklarieren, oder Methoden, die Objekte dieses Typs als Parameter akzeptieren. Eine Methode oder Funktion kann ebenfalls einen Delegattyp zurückgeben. Im folgenden Beispiel akzeptiert die ToCustomString
-Methode den Delegaten als Eingabeparameter. Die Methode ermöglicht Clientcode, eine benutzerdefinierte Funktion bereitzustellen, die eine Zeichenfolge aus einer der oder allen öffentlichen Eigenschaften eines ContactInfo
-Objekts erstellt.
public ref class ContactInfo sealed
{
public:
ContactInfo(){}
ContactInfo(Platform::String^ saluation, Platform::String^ last, Platform::String^ first, Platform::String^ address1);
property Platform::String^ Salutation;
property Platform::String^ LastName;
property Platform::String^ FirstName;
property Platform::String^ Address1;
//...other properties
Platform::String^ ToCustomString(CustomStringDelegate^ func)
{
return func(this);
}
};
Hinweis
Sie verwenden das Symbol "^", wenn Sie auf den Delegattyp verweisen, genau wie bei jedem beliebigen Windows-Runtime Verweistyp.
Eine Ereignisdeklaration verfügt immer über einen Delegattyp. Dieses Beispiel zeigt eine typische Stellvertretungstypsignatur im Windows-Runtime:
public delegate void RoutedEventHandler(
Platform::Object^ sender,
Windows::UI::Xaml::RoutedEventArgs^ e
);
Das Click
-Ereignis in der Windows:: UI::Xaml::Controls::Primitives::ButtonBase
-Klasse ist vom Typ RoutedEventHandler
. Weitere Informationen finden Sie unter Ereignisse.
Clientcode erstellt unter Verwendung von ref new
zunächst die Delegatinstanz und stellt ein Lambda bereit, das mit der Signatur des Delegaten kompatibel ist und das benutzerdefinierte Verhalten definiert.
CustomStringDelegate^ func = ref new CustomStringDelegate([] (ContactInfo^ c)
{
return c->FirstName + " " + c->LastName;
});
Anschließend wird die Memberfunktion aufgerufen und der Delegat übergeben. Angenommen, ci
ist eine ContactInfo^
-Instanz und textBlock
ein XAML- TextBlock^
.
textBlock->Text = ci->ToCustomString( func );
Im nächsten Beispiel übergibt eine Client-App einen benutzerdefinierten Delegaten an eine öffentliche Methode in einer Windows-Runtime Komponente, die den Delegaten für jedes Element in einer:Vector
//Client app
obj = ref new DelegatesEvents::Class1();
CustomStringDelegate^ myDel = ref new CustomStringDelegate([] (ContactInfo^ c)
{
return c->Salutation + " " + c->LastName;
});
IVector<String^>^ mycontacts = obj->GetCustomContactStrings(myDel);
std::for_each(begin(mycontacts), end(mycontacts), [this] (String^ s)
{
this->ContactString->Text += s + " ";
});
// Public method in WinRT component.
IVector<String^>^ Class1::GetCustomContactStrings(CustomStringDelegate^ del)
{
namespace WFC = Windows::Foundation::Collections;
Vector<String^>^ contacts = ref new Vector<String^>();
VectorIterator<ContactInfo^> i = WFC::begin(m_contacts);
std::for_each( i ,WFC::end(m_contacts), [contacts, del](ContactInfo^ ci)
{
contacts->Append(del(ci));
});
return contacts;
}
Bauwesen
Sie können aus jedem der folgenden Objekte einen Delegaten erstellen:
lambda
Statische Funktion
Zeiger auf Member
std::function
Im folgenden Beispiel wird gezeigt, wie aus jedem dieser Objekte ein Delegat erstellt wird. Sie verwenden den Delegaten auf genau die gleiche Weise, unabhängig vom zur Erstellung verwendeten Objekttyp.
ContactInfo^ ci = ref new ContactInfo("Mr.", "Michael", "Jurek", "1234 Compiler Way");
// Lambda. (Avoid capturing "this" or class members.)
CustomStringDelegate^ func = ref new CustomStringDelegate([] (ContactInfo^ c)
{
return c->Salutation + " " + c->FirstName + " " + c->LastName;
});
// Static function.
// static Platform::String^ GetFirstAndLast(ContactInfo^ info);
CustomStringDelegate^ func2 = ref new CustomStringDelegate(Class1::GetFirstAndLast);
// Pointer to member.
// Platform::String^ GetSalutationAndLast(ContactInfo^ info)
CustomStringDelegate^ func3 = ref new CustomStringDelegate(this, &DelegatesEvents::Class1::GetSalutationAndLast);
// std::function
std::function<String^ (ContactInfo^)> f = Class1::GetFirstAndLast;
CustomStringDelegate^ func4 = ref new CustomStringDelegate(f);
// Consume the delegates. Output depends on the
// implementation of the functions you provide.
textBlock->Text = func(ci);
textBlock2->Text = func2(ci);
textBlock3->Text = func3(ci);
textBlock4->Text = func4(ci);
Warnung
Wenn Sie ein Lambda verwenden, das den this-Zeiger aufzeichnet, stellen Sie sicher, dass Sie den -=
-Operator verwenden, um die Registrierung vor Beenden des Lambda explizit vom Ereignis aufzuheben. Weitere Informationen finden Sie unter Ereignisse.
Generische Delegate
Generische Delegaten in C++/CX haben die Einschränkungen, die den Deklarationen von generischen Klassen ähneln. Sie können nicht als öffentlich deklariert werden. Sie können einen privaten oder internen generischen Delegat deklarieren und von C++ verwenden, aber .NET- oder JavaScript-Clients können ihn nicht nutzen, da er nicht in die WINMD-Metadaten ausgegeben wird. In diesem Beispiel wird ein generischer Delegat deklariert, der nur von C++ genutzt werden kann:
generic <typename T>
delegate void MyEventHandler(T p1, T p2);
Im folgenden Beispiel wird eine besondere Instanz des Delegaten innerhalb einer Klassendefinition deklariert:
MyEventHandler<float>^ myDelegate;
Delegaten und Threads
Ein Delegat enthält, genau wie ein Funktionsobjekt, Code, der zu einem späteren Zeitpunkt ausgeführt wird. Wenn der Code, der den Delegaten erstellt und übergibt, und die Funktion, die den Delegaten akzeptiert und ausführt, im selben Thread ausgeführt werden, dann ist der Vorgang relativ einfach. Ist dieser Thread der UI-Thread, kann der Delegat Benutzeroberflächenobjekte wie XAML-Steuerelemente direkt bearbeiten.
Wenn eine Client-App eine Windows-Runtime Komponente lädt, die in einem Thread-Apartment ausgeführt wird und eine Stellvertretung für diese Komponente bereitstellt, wird der Delegat standardmäßig direkt im STA-Thread aufgerufen. Die meisten Windows-Runtime Komponenten können entweder in STA oder MTA ausgeführt werden.
Wenn der Code, der den Delegaten ausführt, in einem anderen Thread ausgeführt wird (beispielsweise im Kontext eines concurrency::task-Objekts), dann sind Sie für das Synchronisieren des Zugriffs auf freigegebene Daten verantwortlich. Wenn der Delegat beispielsweise einen Verweis auf einen Vector enthält, und ein XAML-Steuerelement verfügt über einen Verweis auf denselben Vector, müssen Sie Schritte unternehmen, um Deadlocks oder Racebedingungen zu vermeiden. Diese können auftreten, wenn sowohl der Delegat als auch das XAML-Steuerelement versuchen, gleichzeitig auf den Vector zuzugreifen. Sie müssen außerdem darauf achten, dass der Delegat nicht versucht, durch Verweise lokale Variablen aufzuzeichnen, die vor Aufruf des Delegaten ungültig werden.
Wenn Sie möchten, dass der erstellte Delegat im gleichen Thread wieder aufgerufen wird, in dem er erstellt wurde – beispielsweise, wenn Sie ihn an eine Komponente übergeben, die in einem MTA Apartment ausgeführt wird – und Sie möchten, dass er im gleichen Thread wie der Ersteller aufgerufen wird, dann verwenden Sie die Delegatkonstruktorüberladung, die einen zweiten CallbackContext
-Parameter akzeptiert. Verwenden Sie diese Überladung nur bei Delegaten, die einen registrierten Proxy/Stub haben; nicht alle in Windows.winmd definierten Delegaten sind registriert.
Wenn Sie mit Ereignishandlern in .NET vertraut sind, wissen Sie, dass die empfohlene Vorgehensweise darin besteht, eine lokale Kopie eines Ereignisses zu erstellen, bevor Sie es auslösen. Dies vermeidet Racebedingungen, in denen ein Ereignishandler möglicherweise entfernt würde, bevor das Ereignis aufgerufen wird. Dies ist in C++/CX nicht erforderlich, da beim Hinzufügen oder Entfernen von Ereignishandlern eine neue Handlerliste erstellt wird. Da ein C++-Objekt den Verweiszähler auf der Handlerliste erhöht, bevor ein Ereignis aufgerufen wird, ist sichergestellt, dass alle Handler gültig sind. Dies bedeutet auch, dass, wenn Sie einen Ereignishandler auf dem Consumerthread entfernen, dieser Handler möglicherweise weiterhin aufgerufen werden kann, wenn das Veröffentlichungsobjekt noch auf der Kopie der Liste funktioniert, die nun veraltet ist. Das Veröffentlichungsobjekt ruft die aktualisierte Liste erst bei der nächsten Auslösung des Ereignisses ab.
Siehe auch
Typsystem
C++-/CX-Programmiersprachenreferenz
Referenz zu Namespaces