Délégués (C++/CX)

La delegate mot clé est utilisée pour déclarer un type de référence qui est l’équivalent Windows Runtime d’un objet de fonction en C++standard. Une déclaration delegate est similaire à une signature de fonction. Elle spécifie le type de retour et les types de paramètre que sa fonction incluse dans un wrapper doit comporter. Voici une déclaration delegate définie par l'utilisateur :

public delegate void PrimeFoundHandler(int result);

Les délégués sont plus fréquemment utilisés conjointement avec les événements. Un événement a un type délégué, de la même manière qu'une classe peut avoir un type interface. Le délégué représente un contrat que les gestionnaires d'événements remplissent en grande partie. Voici un membre de classe d’événements dont le type est le délégué précédemment défini :

event PrimeFoundHandler^ primeFoundEvent;

Lors de la déclaration de délégués qui seront exposés aux clients sur l’interface binaire de l’application Windows Runtime, utilisez Windows ::Foundation ::TypedEventHandler<TSender, TResult>. Ce délégué a des binaires stub et proxy prédéfinis qui lui permettent d'être consommé par des clients Javascript.

Utilisation des délégués

Lorsque vous créez une application plateforme Windows universelle, vous travaillez souvent avec un délégué comme type d’événement qu’une classe Windows Runtime expose. Pour s'abonner à un événement, créez une instance de son type délégué en spécifiant une fonction (ou lambda) qui correspond à la signature du délégué. Ensuite, utilisez l'opérateur += pour transmettre l'objet délégué au membre d'événement dans la classe. Ce concept est appelé abonnement à l'événement. Lorsque l'instance de classe « déclenche » l'événement, votre fonction est appelée, ainsi que tous les autres gestionnaires ajoutés par votre objet ou d'autres objets.

Conseil

Visual Studio exécute un volume important de travail lorsque vous créez un gestionnaire d'événements. Par exemple, si vous spécifiez un gestionnaire d'événements dans le balisage XAML, une info-bulle s'affiche. Si vous choisissez l'info-bulle, Visual Studio crée automatiquement la méthode de gestionnaire d'événements et l'associe à l'événement dans la classe d'édition.

L'exemple suivant illustre le modèle de base. Windows::Foundation::TypedEventHandler est le type délégué. La fonction gestionnaire est créée à l'aide d'une fonction nommée.

Dans 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;

};

Dans 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)
    {/*...*/}

}

Avertissement

En général, pour un gestionnaire d'événements, il est préférable d'utiliser une fonction nommée au lieu d'une fonction lambda, à moins que vous ne souhaitiez particulièrement éviter les références circulaires. Une fonction nommée capture le pointeur « this » par une référence faible, mais une fonction lambda le capture par une référence forte et crée une référence circulaire. Pour plus d’informations, consultez Références faibles et cycles cassants.

Par convention, les noms délégués de gestionnaire d’événements définis par Windows Runtime ont le formulaire *EventHandler, par exemple RoutedEventHandler, SizeChangedEventHandler ou SuspendingEventHandler. Également par convention, les délégués de gestionnaire d'événements ont deux paramètres et retournent la valeur void. Dans un délégué sans paramètres de type, le premier paramètre est de type Platform::Object^; il contient une référence à l'expéditeur, qui est l'objet qui a déclenché l'événement. Vous devez effectuer un cast vers le type d'origine avant d'utiliser la méthode de gestionnaire d'argument d'événement. Dans un délégué de gestionnaire d'événements ayant des paramètres de type, le premier paramètre de type spécifie le type de l'expéditeur et le deuxième paramètre est un handle à une classe de référence qui contient des informations sur l'événement. Par convention, cette classe est nommée *EventArgs. Par exemple, un délégué RoutedEventHandler est doté d'un deuxième paramètre de type RoutedEventArgs^, et DragEventHander d'un deuxième paramètre de type DragEventArgs^.

Par convention, les délégués qui encapsulent le code exécuté lorsqu'une opération asynchrone se termine sont nommés *CompletedHandler. Ces délégués sont définis comme propriétés sur la classe, et non en tant qu'événements. De ce fait, n'utilisez pas l'opérateur += pour vous y abonner ; n'assignez qu'un seul objet délégué à la propriété.

Conseil

C++ IntelliSense n'affiche pas la signature du délégué complète ; par conséquent, il ne vous permet pas de déterminer le type spécifique du paramètre EventArgs. Pour rechercher le type, vous pouvez accéder à l' Explorateur d'objets et consulter la méthode Invoke pour le délégué.

Création des délégués personnalisés

Vous pouvez définir vos propres délégués, définir des gestionnaires d’événements ou permettre aux consommateurs de transmettre des fonctionnalités personnalisées à votre composant Windows Runtime. Comme tout autre type Windows Runtime, un délégué public ne peut pas être déclaré comme générique.

Déclaration

La déclaration d'un délégué ressemble à une déclaration de fonction sauf que le délégué est un type. En général, vous déclarez un délégué à la portée d'espace de noms, même si vous pouvez également imbriquer une déclaration de délégué au sein d'une déclaration de classe. Le délégué suivant encapsule une fonction qui prend un ContactInfo^ comme entrée et retourne Platform::String^.

public delegate Platform::String^ CustomStringDelegate(ContactInfo^ ci);

Une fois que vous déclarez un type délégué, vous pouvez déclarer des membres de la classe de ce type ou des méthodes qui prennent des objets de ce type en tant que paramètres. Une méthode ou une fonction peut également retourner un type délégué. Dans l'exemple suivant, la méthode ToCustomString prend le délégué comme paramètre d'entrée. La méthode permet à un code client de fournir une fonction personnalisée qui construit une chaîne à l'aide d'une partie ou de toutes les propriétés publiques d'un objet ContactInfo .

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);
    }       
};

Remarque

Vous utilisez le symbole « ^ » lorsque vous faites référence au type délégué, comme vous le faites avec n’importe quel type de référence Windows Runtime.

Une déclaration d'événement a toujours un type délégué. Cet exemple montre une signature de type délégué classique dans Windows Runtime :

public delegate void RoutedEventHandler(
    Platform::Object^ sender, 
    Windows::UI::Xaml::RoutedEventArgs^ e
    );

L'événement Click de la classe Windows:: UI::Xaml::Controls::Primitives::ButtonBase est de type RoutedEventHandler. Pour plus d’informations, consultez Événements.

Le code client construit d'abord l'instance de délégué en utilisant ref new et en fournissant une valeur lambda qui est compatible avec la signature du délégué et définit le comportement personnalisé.

CustomStringDelegate^ func = ref new CustomStringDelegate([] (ContactInfo^ c)
{
    return c->FirstName + " " + c->LastName;
});

Il appelle ensuite la fonction membre et passe le délégué. Supposons que ci est une instance de ContactInfo^ et que textBlock est un TextBlock^XAML.

textBlock->Text = ci->ToCustomString( func );

Dans l’exemple suivant, une application cliente transmet un délégué personnalisé à une méthode publique dans un composant Windows Runtime qui exécute le délégué sur chaque élément d’un 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;
}

Construction

Vous pouvez construire un délégué à partir de l'un de ces objets :

  • lambda

  • fonction statique

  • pointeur vers membre

  • std::function

L'exemple suivant montre comment construire un délégué à partir de chacun de ces objets. Vous consommez le délégué de la même façon quel que soit le type d'objet utilisé pour la construction.


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);

Avertissement

Si vous utilisez une fonction lambda qui capture le pointeur « this », veillez à utiliser l'opérateur -= pour annuler explicitement votre inscription à l'événement avant de quitter la fonction lambda. Pour plus d’informations, consultez Événements.

Délégués génériques

Les délégués génériques en C++/CX ont des restrictions similaires à celles des déclarations de classes génériques. Ils ne peuvent pas être déclarés publics. Vous pouvez déclarer un délégué générique privé ou interne et l’utiliser à partir de C++, mais les clients .NET ou JavaScript ne peuvent pas l’utiliser, car il n’est pas émis dans les métadonnées .winmd. Cet exemple déclare un délégué générique qui ne peut être consommé que par du C++ :

generic <typename T>
delegate void  MyEventHandler(T p1, T p2);

L'exemple suivant déclare une instance spécifique du délégué, à l'intérieur d'une définition de classe :

MyEventHandler<float>^ myDelegate;

Délégués et threads

Un délégué, comme un objet de fonction, contient le code qui s'exécute à un moment donné dans le futur. Si le code qui crée et passe le délégué, ainsi que la fonction qui accepte et exécute le délégué, s'exécutent sur le même thread, alors les choses sont relativement simples. Si ce thread est le thread d'interface utilisateur, le délégué peut directement manipuler des objets d'interface utilisateur tels que les contrôles XAML.

Si une application cliente charge un composant Windows Runtime qui s’exécute dans un appartement threadé et fournit un délégué à ce composant, par défaut, le délégué est appelé directement sur le thread STA. La plupart des composants Windows Runtime peuvent s’exécuter dans STA ou MTA.

Si le code qui exécute le délégué s'exécute sur un autre thread (par exemple, dans le contexte d'un objet concurrency::task), vous êtes alors chargé de synchroniser l'accès aux données partagées. Par exemple, si votre délégué contient une référence à un vecteur et qu'un contrôle XAML possède une référence à ce même vecteur, vous devez prendre les mesures nécessaires pour éviter les interblocages et les conditions de concurrence critiques qui peuvent se produire lorsque le délégué et le contrôle XAML tentent d'accéder au vecteur simultanément. Vous devez également veiller à ce que le délégué ne tente pas de capturer les variables locales par référence pouvant être hors de portée avant que le délégué ne soit appelé.

Si vous souhaitez que votre délégué créé soit rappelé sur le thread sur lequel il a été créé, par exemple, si vous le passez à un composant qui s'exécute dans un MTA cloisonné, et que vous souhaitez qu'il soit appelé sur le même thread que le créateur, utilisez la surcharge de constructeur délégué qui accepte un second paramètre CallbackContext . Utilisez uniquement cette surcharge sur les délégués avec un proxy/stub inscrits car tous les délégués qui sont définis dans Windows.winmd ne sont pas inscrits.

Si vous connaissez les gestionnaires d'événements dans .NET, vous savez qu'il est recommandé de créer une copie locale d'un événement avant de le déclencher. Cela évite les conditions de concurrence où un gestionnaire d'événements peut être supprimé juste avant l'appel de l'événement. Il n’est pas nécessaire de le faire en C++/CX, car lorsque des gestionnaires d’événements sont ajoutés ou supprimés, une nouvelle liste de gestionnaires est créée. Dans la mesure où un objet C++ incrémente le nombre de références dans la liste de gestionnaires avant d'appeler un événement, vous avez la garantie que tous les gestionnaires sont valides. Toutefois, cela signifie également que si vous supprimez un gestionnaire d'événements sur le thread consommateur, ce gestionnaire peut encore être appelé si l'objet de publication continue d'utiliser sa copie de la liste, laquelle est désormais obsolète. L'objet de publication obtiendra la mise à jour de la liste la prochaine fois qu'il déclenchera l'événement.

Voir aussi

Système de type
Informations de référence sur le langage C++/CX
Référence aux espaces de noms