Delegati (C++/CX)

La delegate parola chiave viene usata per dichiarare un tipo di riferimento equivalente a Windows Runtime di un oggetto funzione in C++standard. Dichiarazione di un delegato simile a una firma della funzione. Specifica il tipo restituito e i tipi di parametro per la relativa funzione di cui è stato eseguito il wrapping. Si tratta di una dichiarazione di un delegato definita dall'utente:

public delegate void PrimeFoundHandler(int result);

I delegati sono utilizzati in genere insieme agli eventi. Un evento dispone di un tipo delegato, nello stesso modo in cui una classe può avere un tipo di interfaccia. Il delegato rappresenta un contratto che gestori eventi deve soddisfare. Ecco un membro della classe di evento il cui tipo è il delegato definito in precedenza:

event PrimeFoundHandler^ primeFoundEvent;

Quando si dichiarano delegati che verranno esposti ai client nell'interfaccia binaria dell'applicazione Windows Runtime, usare Windows::Foundation::TypedEventHandler<TSender, TResult>. I binari proxy e stub usati come prefisso per questo delegato ne permettono l'uso da parte di client Javascript.

Utilizzo dei delegati

Quando crei un'app piattaforma UWP (Universal Windows Platform), spesso lavori con un delegato come tipo di evento esposto da una classe Windows Runtime. Per sottoscrivere un evento, crea un'istanza del relativo tipo di delegato specificando una funzione, o lambda, che corrisponda alla firma del delegato. Utilizza quindi l'operatore += per passare l'oggetto delegato al membro dell'evento nella classe. Questo processo è noto come sottoscrizione dell'evento. Quando l'istanza della classe genera l'evento, viene chiamata la funzione insieme a tutti gli altri gestori aggiunti dal tuo oggetto o da altri oggetti.

Suggerimento

In Visual Studio molte operazioni vengono eseguite automaticamente durante la creazione di un gestore eventi. Ad esempio, se specifichi un gestore eventi nel markup XAML, compare una descrizione comandi. Se scegli la descrizione comandi, viene creato automaticamente il metodo del gestore eventi, che viene quindi associato all'evento nella classe di pubblicazione.

L'esempio seguente mostra il modello di base. Windows::Foundation::TypedEventHandler è il tipo delegato. La funzione del gestore viene creata utilizzando una funzione denominata.

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

}

Avviso

In genere, per un gestore di eventi è preferibile utilizzare una funzione denominata anziché una funzione lambda, a meno di non prestare molta attenzione a evitare i riferimenti circolari. Una funzione denominata acquisisce il puntatore "this" per riferimento debole, ma una funzione lambda lo acquisisce per riferimento forte e crea un riferimento circolare. Per altre informazioni, vedere Riferimenti deboli e cicli di interruzione.

Per convenzione, i nomi dei delegati del gestore eventi definiti da Windows Runtime hanno il formato *EventHandler, ad esempio RoutedEventHandler, SizeChangedEventHandler o SuspendingEventHandler. Sempre per convenzione, i delegati del gestore eventi accettano due parametri e restituiscono void. In un delegato privo di parametri di tipo, il primo parametro è di tipo Platform::Object^e contiene un riferimento al mittente, ossia l'oggetto che ha generato l'evento. Prima di utilizzare l'argomento nel metodo del gestore dell'evento, devi eseguire il cast del tipo originale. In un delegato del gestore eventi che dispone di parametri di tipo, il primo parametro di tipo specifica il tipo del mittente, mentre il secondo è un handle a una classe di riferimento che contiene informazioni sull'evento. Per convenzione, tale classe è denominata *EventArgs. Ad esempio, un delegato RoutedEventHandler dispone di un secondo parametro di tipo RoutedEventArgs^ e DragEventHander dispone di un secondo parametro di tipo DragEventArgs^.

Per convenzione, i delegati che eseguono il wrapping del codice eseguito quando viene completata un'operazione asincrona sono denominati *CompletedHandler. Questi delegati sono definiti come proprietà della classe, non come eventi. Pertanto, non utilizzare l'operatore += per sottoscriverli; assegna semplicemente un oggetto delegato alla proprietà.

Suggerimento

IntelliSense per C++ non visualizza la firma completa del delegato, pertanto non consente di determinare il tipo specifico del parametro EventArgs. Per trovare il tipo, puoi passare al Visualizzatore oggetti ed esaminare il metodo Invoke per il delegato.

Creazione di delegati personalizzati

Puoi definire delegati personalizzati, definire gestori eventi o consentire ai consumer di passare funzionalità personalizzate al componente Windows Runtime. Come qualsiasi altro tipo di Windows Runtime, un delegato pubblico non può essere dichiarato come generico.

Dichiarazione

La dichiarazione di un delegato è simile a una dichiarazione di funzione eccetto per il fatto che il delegato è un tipo. In genere, la dichiarazione di un delegato viene eseguita nell'ambito dello spazio dei nomi, sebbene sia possibile anche annidarla in una dichiarazione di classe. Il seguente delegato incapsula qualsiasi funzione che accetta un oggetto ContactInfo^ come input e restituisce Platform::String^.

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

Una volta dichiarato un tipo di delegato, puoi dichiarare i membri della classe dello stesso tipo o metodi che accettano oggetti del tipo specificato come parametri. Un metodo o una funzione può restituire anche un tipo di delegato. Nell'esempio seguente, il parametro di input del metodo ToCustomString è un delegato. Il metodo consente al codice client di fornire una funzione personalizzata che crea una stringa utilizzando alcune o tutte le proprietà pubbliche dell'oggetto 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);
    }       
};

Nota

Il simbolo "^" viene usato quando si fa riferimento al tipo delegato, proprio come avviene con qualsiasi tipo di riferimento di Windows Runtime.

Una dichiarazione di evento contiene sempre un tipo di delegato. Questo esempio mostra una tipica firma del tipo delegato in Windows Runtime:

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

L'evento Click nella classe Windows:: UI::Xaml::Controls::Primitives::ButtonBase è di tipo RoutedEventHandler. Per altre informazioni, vedere Events.

Tramite il codice client viene innanzitutto creata l'istanza del delegato utilizzando ref new e fornendo un'espressione lambda compatibile con la firma del delegato e successivamente viene definito il comportamento personalizzato.

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

Il codice chiama quindi la funzione membro e passa il delegato. Supponi che ci sia un'istanza di ContactInfo^ e che textBlock sia un oggetto TextBlock^XAML.

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

Nell'esempio seguente un'app client passa un delegato personalizzato a un metodo pubblico in un componente Windows Runtime che esegue il delegato su ogni elemento in un Vectoroggetto :

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

Edilizia

Puoi creare un delegato da uno qualsiasi di questi oggetti:

  • funzione lambda

  • funzione statica

  • puntatore a membro

  • std::function

Nell'esempio riportato di seguito viene illustrato come costruire un delegato da ognuno di questi oggetti. Puoi utilizzare il delegato esattamente nello stesso modo indipendentemente dal tipo di oggetto utilizzato per crearlo.


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

Avviso

Se usi un'espressione lambda che acquisisce il puntatore "this", assicurati di utilizzare l'operatore -= per annullare in modo esplicito la registrazione dall'evento prima di uscire da lambda. Per altre informazioni, vedere Events.

Delegati generici

I delegati generici in C++/CX presentano restrizioni simili alle dichiarazioni di classi generiche. Non possono essere dichiarati come pubblici. È possibile dichiarare un delegato generico privato o interno e usarlo da C++, ma i client .NET o JavaScript non possono utilizzarlo perché non viene generato nei metadati winmd. Questo esempio dichiara un delegato generico che può essere utilizzato solo da C++:

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

L'esempio successivo dichiara un'istanza specifica del delegato all'interno di una definizione di classe:

MyEventHandler<float>^ myDelegate;

Delegati e thread

Un delegato, analogamente a un oggetto funzione, contiene del codice che verrà eseguito in un momento futuro. Se il codice che crea e passa il delegato e la funzione che accetta ed esegue il delegato sono in esecuzione nello stesso thread, diventa tutto relativamente semplice. Se il thread è il thread UI, gli oggetti dell'interfaccia utente come i controlli XAML possono essere modificati direttamente dal delegato.

Se un'app client carica un componente Windows Runtime che viene eseguito in un apartment a thread e fornisce un delegato a tale componente, per impostazione predefinita il delegato viene richiamato direttamente sul thread STA. La maggior parte dei componenti Windows Runtime può essere eseguita in STA o MTA.

Se il codice che esegue il delegato è in esecuzione in un thread differente, ad esempio all'interno di un contesto di oggetto concurrency::task, sarà necessario sincronizzare l'accesso ai dati condivisi. Ad esempio, se il delegato e un controllo XAML contengono un riferimento a uno stesso vettore, è necessario evitare i deadlock e le race condition che potrebbero verificarsi nel momento in cui sia il delegato che il controllo XAML tentano di accedere contemporaneamente al vettore. Devi inoltre assicurarti che il delegato non tenti di acquisire tramite riferimenti le variabili locali che potrebbe uscire di ambito prima che il delegato sia richiamato.

Se desideri che il delegato sia chiamato di nuovo sullo stesso thread in cui è stato creato, ad esempio se lo passi a un componente che viene eseguito in apartment MTA, e desideri che venga richiamato sullo stesso thread dell'autore, utilizza l'overload del costruttore di delegato che accetta un secondo parametro CallbackContext . Utilizza questo overload solo su delegati che dispongono di un proxy o uno stub registrato. Non tutti i delegati definiti in Windows.winmd sono registrati.

Se hai familiarità con i gestori eventi di .NET, saprai che è preferibile creare una copia locale di un evento prima di generarlo. Questo evita race condition in cui un gestore eventi viene rimosso appena prima che l'evento venga richiamato. Non è necessario eseguire questa operazione in C++/CX perché quando vengono aggiunti o rimossi gestori eventi viene creato un nuovo elenco di gestori. Poiché un oggetto c++ incrementa il conteggio dei riferimenti nell'elenco di gestori prima di richiamare un evento, viene garantito che i gestori saranno validi. Tuttavia, questo significa anche che se rimuovi un gestore eventi nel thread consumer, il gestore può comunque continuare a essere richiamato se l'oggetto di pubblicazione è ancora in esecuzione nella propria copia dell'elenco, che a questo punto non è aggiornato. L'oggetto di pubblicazione non riceverà l'elenco aggiornato fino alla successiva generazione dell'evento.

Vedi anche

Sistema di tipi
Riferimenti al linguaggio C++/CX
Riferimenti a spazi dei nomi