Delegaty (C++/CX)

Słowo delegate kluczowe służy do deklarowania typu odwołania, który jest środowisko wykonawcze systemu Windows odpowiednik obiektu funkcji w standardowym języku C++. Deklaracja delegata podobna do podpisu funkcji; Określa typ zwracany i typy parametrów, które musi mieć jego opakowana funkcja. Jest to deklaracja delegata zdefiniowana przez użytkownika:

public delegate void PrimeFoundHandler(int result);

Delegaty są najczęściej używane w połączeniu ze zdarzeniami. Zdarzenie ma typ delegata w taki sam sposób, jak klasa może mieć typ interfejsu. Delegat reprezentuje kontrakt, który obsługuje zdarzenia w dużym stopniu. Oto składowa klasy zdarzeń, której typem jest wcześniej zdefiniowany delegat:

event PrimeFoundHandler^ primeFoundEvent;

Podczas deklarowania delegatów, które będą widoczne dla klientów w interfejsie binarnym aplikacji środowisko wykonawcze systemu Windows, użyj polecenia Windows::Foundation::TypedEventHandler<TSender, TResult>. Ten delegat ma wstępnie zdefiniowany serwer proxy i pliki binarne wycinków, które umożliwiają korzystanie z niego przez klientów języka JavaScript.

Korzystanie z delegatów

Podczas tworzenia aplikacji platforma uniwersalna systemu Windows często pracujesz z pełnomocnikiem jako typem zdarzenia, które uwidacznia klasa środowisko wykonawcze systemu Windows. Aby zasubskrybować zdarzenie, utwórz wystąpienie jego typu delegata, określając funkcję (lub lambda) zgodną z podpisem delegata. Następnie użyj += operatora , aby przekazać obiekt delegata do składowej zdarzenia w klasie. Jest to nazywane subskrybowaniem zdarzenia. Gdy wystąpienie klasy "uruchamia" zdarzenie, wywoływana jest funkcja wraz z innymi procedurami obsługi dodanymi przez obiekt lub inne obiekty.

Napiwek

Program Visual Studio wykonuje wiele pracy podczas tworzenia programu obsługi zdarzeń. Jeśli na przykład określisz procedurę obsługi zdarzeń w znaczników XAML, zostanie wyświetlona porada narzędzia. Jeśli wybierzesz poradę narzędzia, program Visual Studio automatycznie utworzy metodę obsługi zdarzeń i skojarzy ją ze zdarzeniem w klasie publikowania.

W poniższym przykładzie przedstawiono podstawowy wzorzec. Windows::Foundation::TypedEventHandler jest typem delegata. Funkcja obsługi jest tworzona przy użyciu nazwanej funkcji.

W pliku 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;

};

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

}

Ostrzeżenie

Ogólnie rzecz biorąc, w przypadku obsługi zdarzeń lepiej jest użyć nazwanej funkcji zamiast lambda, chyba że należy zachować szczególną ostrożność, aby uniknąć odwołań cyklicznego. Nazwana funkcja przechwytuje wskaźnik "this" przez słabe odwołanie, ale lambda przechwytuje go przez silne odwołanie i tworzy odwołanie cykliczne. Aby uzyskać więcej informacji, zobacz Słabe odwołania i cykle przerwania.

Zgodnie z konwencją nazwy delegatów programu obsługi zdarzeń zdefiniowane przez środowisko wykonawcze systemu Windows mają formularz *EventHandler — na przykład RoutedEventHandler, SizeChangedEventHandler lub SuspendingEventHandler. Również zgodnie z konwencją delegaty programu obsługi zdarzeń mają dwa parametry i zwracają void. W delegatu, który nie ma parametrów typu, pierwszy parametr jest typu Platform::Object^; zawiera odwołanie do nadawcy, który jest obiektem, który wyzwolił zdarzenie. Przed użyciem argumentu w metodzie obsługi zdarzeń należy wrócić do oryginalnego typu. W delegatu programu obsługi zdarzeń, który ma parametry typu, pierwszy parametr typu określa typ nadawcy, a drugi parametr jest dojściem do klasy ref, która zawiera informacje o zdarzeniu. Zgodnie z konwencją ta klasa ma nazwę *EventArgs. Na przykład delegat RoutedEventHandler ma drugi parametr typu RoutedEventArgs^, a dragEventHander ma drugi parametr typu DragEventArgs^.

Zgodnie z konwencją delegaty, które opakowują kod wykonywany po zakończeniu operacji asynchronicznej, mają nazwę *CompletedHandler. Te delegaty są definiowane jako właściwości klasy, a nie jako zdarzenia. W związku z tym nie używasz += operatora do subskrybowania ich; wystarczy przypisać obiekt delegata do właściwości.

Napiwek

Funkcja IntelliSense języka C++ nie wyświetla pełnego podpisu delegata; w związku z tym nie pomaga określić określonego typu parametru EventArgs. Aby znaleźć typ, możesz przejść do przeglądarki obiektów i przyjrzeć się Invoke metodzie delegata.

Tworzenie delegatów niestandardowych

Możesz zdefiniować własnych delegatów, aby zdefiniować programy obsługi zdarzeń lub umożliwić użytkownikom przekazywanie niestandardowych funkcji do składnika środowisko wykonawcze systemu Windows. Podobnie jak w przypadku każdego innego typu środowisko wykonawcze systemu Windows, delegat publiczny nie może być zadeklarowany jako ogólny.

Deklaracji

Deklaracja delegata przypomina deklarację funkcji, z tą różnicą, że delegat jest typem. Zazwyczaj deklarujesz delegata w zakresie przestrzeni nazw, chociaż można również zagnieżdżać deklarację delegata w deklaracji klasy. Poniższy delegat hermetyzuje dowolną ContactInfo^ funkcję, która przyjmuje element jako dane wejściowe i zwraca wartość Platform::String^.

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

Po zadeklarowaniu typu delegata można zadeklarować elementy członkowskie klasy tego typu lub metody, które przyjmują obiekty tego typu jako parametry. Metoda lub funkcja może również zwrócić typ delegata. W poniższym przykładzie ToCustomString metoda przyjmuje delegata jako parametr wejściowy. Metoda umożliwia kodowi klienta podanie funkcji niestandardowej, która tworzy ciąg z niektórych lub wszystkich właściwości ContactInfo publicznych obiektu.

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

Uwaga

Symbol "^" jest używany podczas odwoływania się do typu delegata, podobnie jak w przypadku dowolnego typu odwołania środowisko wykonawcze systemu Windows.

Deklaracja zdarzenia zawsze ma typ delegata. W tym przykładzie przedstawiono typowy podpis typu delegata w środowisko wykonawcze systemu Windows:

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

Zdarzenie Click w Windows:: UI::Xaml::Controls::Primitives::ButtonBase klasie jest typu RoutedEventHandler. Aby uzyskać więcej informacji, zobacz Zdarzenia.

Kod klienta najpierw konstruuje wystąpienie delegata przy użyciu i ref new udostępnia lambda zgodne z podpisem delegowanym i definiuje zachowanie niestandardowe.

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

Następnie wywołuje funkcję składową i przekazuje delegata. Załóżmy, że ci jest wystąpieniem ContactInfo^ i textBlock jest XAML TextBlock^.

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

W następnym przykładzie aplikacja kliencka przekazuje delegata niestandardowego do metody publicznej w składniku środowisko wykonawcze systemu Windows, który wykonuje delegata względem każdego elementu w obiekcie 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;
}

Budownictwo

Można utworzyć delegata z dowolnego z tych obiektów:

  • lambda

  • funkcja statyczna

  • wskaźnik do elementu członkowskiego

  • std::function

W poniższym przykładzie pokazano, jak utworzyć delegata z każdego z tych obiektów. Delegat jest używany w dokładnie taki sam sposób, niezależnie od typu obiektu używanego do jego konstruowania.


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

Ostrzeżenie

Jeśli używasz wyrażenia lambda, który przechwytuje wskaźnik "this", pamiętaj, aby użyć -= operatora , aby jawnie cofnąć rejestrację ze zdarzenia przed wyjściem lambda. Aby uzyskać więcej informacji, zobacz Zdarzenia.

Delegaci ogólni

Delegaty ogólne w języku C++/CX mają ograniczenia podobne do deklaracji klas ogólnych. Nie można ich zadeklarować jako publiczne. Można zadeklarować prywatny lub wewnętrzny delegat ogólny i korzystać z niego z języka C++, ale klienci platformy .NET lub JavaScript nie mogą go używać, ponieważ nie jest emitowany do metadanych winmd. W tym przykładzie zadeklarowano delegata ogólnego, który może być używany tylko przez język C++:

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

W następnym przykładzie zadeklarowane jest wyspecjalizowane wystąpienie delegata wewnątrz definicji klasy:

MyEventHandler<float>^ myDelegate;

Delegaty i wątki

Delegat, podobnie jak obiekt funkcji, zawiera kod, który będzie wykonywany w pewnym momencie w przyszłości. Jeśli kod, który tworzy i przekazuje delegata, oraz funkcja, która akceptuje i wykonuje delegata, są uruchamiane w tym samym wątku, elementy są stosunkowo proste. Jeśli ten wątek jest wątkiem interfejsu użytkownika, delegat może bezpośrednio manipulować obiektami interfejsu użytkownika, takimi jak kontrolki XAML.

Jeśli aplikacja kliencka ładuje składnik środowisko wykonawcze systemu Windows uruchamiany w wątkowym mieszkaniu i udostępnia delegata do tego składnika, domyślnie delegat jest wywoływany bezpośrednio w wątku STA. Większość środowisko wykonawcze systemu Windows składników może działać w sta lub MTA.

Jeśli kod, który wykonuje delegata, jest uruchomiony w innym wątku — na przykład w kontekście obiektu współbieżności::task — odpowiadasz za synchronizowanie dostępu do udostępnionych danych. Jeśli na przykład delegat zawiera odwołanie do wektora, a kontrolka XAML ma odwołanie do tego samego wektora, należy wykonać kroki, aby uniknąć zakleszczenia lub warunków wyścigu, które mogą wystąpić, gdy zarówno delegat, jak i kontrolka XAML próbują uzyskać dostęp do wektora w tym samym czasie. Należy również zadbać o to, aby delegat nie próbował przechwycić przez odwołania do zmiennych lokalnych, które mogą wyjść z zakresu przed wywołaniem delegata.

Jeśli chcesz, aby utworzony pełnomocnik był wywoływany z powrotem w tym samym wątku, w którym został utworzony — na przykład jeśli przekazujesz go do składnika uruchamianego w mieszkaniu MTA — i chcesz, aby był wywoływany w tym samym wątku co twórca, użyj przeciążenia konstruktora delegata, który przyjmuje drugi CallbackContext parametr. Tego przeciążenia należy używać tylko na delegatach, które mają zarejestrowany serwer proxy/wycink; nie wszystkie delegaty zdefiniowane w systemie Windows.winmd są zarejestrowane.

Jeśli znasz programy obsługi zdarzeń na platformie .NET, wiesz, że zalecaną praktyką jest utworzenie lokalnej kopii zdarzenia przed jego uruchomieniem. Pozwala to uniknąć warunków wyścigu, w których program obsługi zdarzeń może zostać usunięty tuż przed wywołaniem zdarzenia. Nie jest to konieczne w języku C++/CX, ponieważ po dodaniu lub usunięciu nowej listy procedur obsługi zdarzeń. Ponieważ obiekt języka C++ zwiększa liczbę odwołań na liście procedur obsługi przed wywołaniem zdarzenia, gwarantuje to, że wszystkie programy obsługi będą prawidłowe. Oznacza to jednak również, że jeśli usuniesz program obsługi zdarzeń w wątku zużywający, program obsługi może nadal być wywoływany, jeśli obiekt publikowania nadal działa na jego kopii listy, która jest teraz nieaktualna. Obiekt publikowania nie otrzyma zaktualizowanej listy do momentu następnego uruchomienia zdarzenia.

Zobacz też

System typów
Dokumentacja języka C++/CX
Dokumentacja przestrzeni nazw