Delegáti (C++/CX)
Klíčové delegate
slovo se používá k deklaraci typu odkazu, který je prostředí Windows Runtime ekvivalentem objektu funkce ve standardním jazyce C++. Deklarace delegáta podobná podpisu funkce; určuje návratový typ a typy parametrů, které musí mít zabalená funkce. Toto je deklarace delegáta definovaná uživatelem:
public delegate void PrimeFoundHandler(int result);
Delegáti se nejčastěji používají ve spojení s událostmi. Událost má typ delegáta, podobně jako třída může mít typ rozhraní. Delegát představuje kontrakt, který obslužné rutiny událostí velmi plní. Tady je člen třídy události, jehož typ je dříve definovaný delegát:
event PrimeFoundHandler^ primeFoundEvent;
Při deklarování delegátů, kteří budou vystaveni klientům v binárním rozhraní prostředí Windows Runtime aplikace, použijte Windows::Foundation::TypedEventHandler<TSender, TResult>. Tento delegát má předdefinované binární soubory proxy a zástupných procedur, které umožňují jeho využívání klienty JavaScriptu.
Využívání delegátů
Při vytváření Univerzální platforma Windows aplikace často pracujete s delegátem jako typem události, kterou prostředí Windows Runtime třída zveřejňuje. Pokud se chcete přihlásit k odběru události, vytvořte instanci svého typu delegáta zadáním funkce (nebo lambda), která odpovídá podpisu delegáta. Potom pomocí operátoru +=
předejte objekt delegáta členovi události ve třídě. To se označuje jako přihlášení k odběru události. Když instance třídy "aktivuje" událost, volá se vaše funkce spolu s dalšími obslužnými rutinami, které byly přidány objektem nebo jinými objekty.
Tip
Visual Studio za vás při vytváření obslužné rutiny události hodně pracuje. Pokud například zadáte obslužnou rutinu události v kódu XAML, zobrazí se popis nástroje. Pokud zvolíte popis nástroje, Visual Studio automaticky vytvoří metodu obslužné rutiny události a přidruží ji k události ve třídě publikování.
Následující příklad ukazuje základní vzor. Windows::Foundation::TypedEventHandler
je typ delegáta. Funkce obslužné rutiny se vytvoří pomocí pojmenované funkce.
V 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;
};
V 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)
{/*...*/}
}
Upozorňující
Obecně platí, že pro obslužnou rutinu události je lepší použít pojmenovanou funkci místo lambda, pokud se nestaráte o to, abyste se vyhnuli cyklický odkaz. Pojmenovaná funkce zachycuje ukazatel "this" slabým odkazem, ale lambda ho zachytí silným odkazem a vytvoří cyklický odkaz. Další informace najdete v tématu Slabé odkazy a cykly způsobující chybu.
Podle konvence mají názvy delegátů obslužné rutiny událostí definované prostředí Windows Runtime mají formát *EventHandler – například RoutedEventHandler, SizeChangedEventHandler nebo SuspendingEventHandler. Delegáti obslužné rutiny událostí mají také dva parametry a vrací void. V delegátu, který nemá parametry typu, je první parametr typu Platform::Object^; obsahuje odkaz na odesílatele, což je objekt, který událost aktivoval. Před použitím argumentu v metodě obslužné rutiny události musíte přetypovat zpět na původní typ. V delegát obslužné rutině události, který má parametry typu, první typ parametr určuje typ odesílatele a druhý parametr je popisovač třídy ref, která obsahuje informace o události. Podle konvence má tato třída název *EventArgs. Například delegát RoutedEventHandler má druhý parametr typu RoutedEventArgs^a DragEventHander má druhý parametr typu DragEventArgs^.
Podle konvence delegáti, kteří zabalí kód, který se spustí při dokončení asynchronní operace, mají název *CompletedHandler. Tito delegáti jsou definováni jako vlastnosti třídy, nikoli jako události. Operátor proto nepoužíváte +=
k přihlášení k odběru, stačí k vlastnosti přiřadit objekt delegáta.
Tip
C++ IntelliSense nezobrazuje úplný podpis delegáta; proto vám nepomůže určit konkrétní typ parametru EventArgs. Typ najdete tak, že přejdete do prohlížeče objektů a podíváte se na metodu Invoke
delegáta.
Vytváření vlastních delegátů
Můžete definovat vlastní delegáty, definovat obslužné rutiny událostí nebo umožnit uživatelům předávat vlastní funkce do prostředí Windows Runtime komponenty. Stejně jako jakýkoli jiný typ prostředí Windows Runtime nelze veřejný delegát deklarovat jako obecný.
Deklarace
Deklarace delegáta se podobá deklaraci funkce s výjimkou toho, že delegát je typ. Obvykle deklarujete delegáta v oboru názvů, i když můžete také vnořit deklaraci delegáta do deklarace třídy. Následující delegát zapouzdřuje jakoukoli funkci, která přebírá ContactInfo^
jako vstup a vrací Platform::String^
hodnotu .
public delegate Platform::String^ CustomStringDelegate(ContactInfo^ ci);
Jakmile deklarujete typ delegáta, můžete deklarovat členy třídy tohoto typu nebo metody, které přebírají objekty tohoto typu jako parametry. Metoda nebo funkce může také vrátit typ delegáta. V následujícím příkladu ToCustomString
metoda přebírá delegáta jako vstupní parametr. Metoda umožňuje klientskému kódu poskytnout vlastní funkci, která vytvoří řetězec z některých nebo všech veřejných vlastností objektu 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);
}
};
Poznámka:
Symbol ^použijete, když odkazujete na typ delegáta, stejně jako u jakéhokoli typu odkazu prostředí Windows Runtime.
Deklarace události má vždy typ delegáta. Tento příklad ukazuje typický podpis typu delegáta v prostředí Windows Runtime:
public delegate void RoutedEventHandler(
Platform::Object^ sender,
Windows::UI::Xaml::RoutedEventArgs^ e
);
Událost Click
ve Windows:: UI::Xaml::Controls::Primitives::ButtonBase
třídě je typu RoutedEventHandler
. Další informace naleznete v tématu Události.
Klientský kód nejprve vytvoří instanci delegáta pomocí ref new
a poskytnutí lambda, která je kompatibilní s podpisem delegáta a definuje vlastní chování.
CustomStringDelegate^ func = ref new CustomStringDelegate([] (ContactInfo^ c)
{
return c->FirstName + " " + c->LastName;
});
Potom zavolá členovu funkci a předá delegáta. Předpokládejme, že ci
je ContactInfo^
to instance a textBlock
je XAML TextBlock^
.
textBlock->Text = ci->ToCustomString( func );
V dalším příkladu klientská aplikace předá vlastního delegáta veřejné metodě v komponentě prostředí Windows Runtime, která spustí delegáta proti každé položce v 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;
}
Stavebnictví
Delegáta můžete vytvořit z některého z těchto objektů:
lambda
statická funkce
ukazatel na člena
std::function
Následující příklad ukazuje, jak vytvořit delegáta z každého z těchto objektů. Delegáta používáte úplně stejným způsobem bez ohledu na typ objektu, který se používá k jeho vytvoření.
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);
Upozorňující
Pokud použijete lambda, která zachycuje ukazatel "tento", nezapomeňte před ukončením lambda explicitně -=
zrušit registraci z události pomocí operátoru. Další informace naleznete v tématu Události.
Obecné delegáty
Obecné delegáty v jazyce C++/CX mají omezení podobná deklaracím obecných tříd. Nelze je deklarovat jako veřejné. Můžete deklarovat privátního nebo interního obecného delegáta a využívat ho z jazyka C++, ale klienti .NET nebo JavaScript ho nemůžou využívat, protože se negenerují do metadat .winmd. Tento příklad deklaruje obecný delegát, který může využívat pouze jazyk C++:
generic <typename T>
delegate void MyEventHandler(T p1, T p2);
Další příklad deklaruje specializovanou instanci delegáta uvnitř definice třídy:
MyEventHandler<float>^ myDelegate;
Delegáti a vlákna
Delegát, stejně jako objekt funkce, obsahuje kód, který se v budoucnu spustí v určitém okamžiku. Pokud kód, který vytvoří a předá delegáta, a funkce, která přijímá a spouští delegáta, běží ve stejném vlákně, pak jsou věci poměrně jednoduché. Pokud je toto vlákno vlákno uživatelského rozhraní, může delegát přímo manipulovat s objekty uživatelského rozhraní, jako jsou ovládací prvky XAML.
Pokud klientská aplikace načte komponentu prostředí Windows Runtime, která běží v apartmánu s vlákny, a poskytuje delegáta této součásti, ve výchozím nastavení se delegát vyvolá přímo ve vlákně STA. Většina komponent prostředí Windows Runtime může běžet v STA nebo MTA.
Pokud je kód, který spouští delegáta, spuštěn v jiném vlákně , například v kontextu souběžnosti::task objekt, pak zodpovídáte za synchronizaci přístupu ke sdíleným datům. Pokud například váš delegát obsahuje odkaz na vektor a ovládací prvek XAML má odkaz na stejný vektor, musíte podniknout kroky, abyste se vyhnuli zablokování nebo časování podmínek, ke kterým může dojít, když se delegát i ovládací prvek XAML pokusí o přístup k vektoru současně. Musíte se také postarat o to, aby se delegát nepokoušel zachytit odkazem na místní proměnné, které můžou před vyvoláním delegáta přejít mimo rozsah.
Pokud chcete, aby byl vytvořený delegát volán zpět ve stejném vlákně, ve kterém byl vytvořen , například pokud ho předáte komponentě, která běží v apartmánu MTA, a chcete ho vyvolat ve stejném vlákně jako tvůrce, použijte přetížení konstruktoru delegáta, který přebírá druhý CallbackContext
parametr. Toto přetížení použijte pouze u delegátů, kteří mají zaregistrovaný proxy/zástupný proceduru; nejsou zaregistrovaní všichni delegáti, kteří jsou definováni ve Windows.winmd.
Pokud znáte obslužné rutiny událostí v .NET, víte, že doporučeným postupem je vytvořit místní kopii události před jejím spuštěním. Tím se vyhnete okolnostem časování, kdy obslužná rutina události může být odebrána těsně před vyvolání události. V jazyce C++/CX to není nutné udělat, protože při přidání nebo odebrání obslužných rutin se vytvoří nový seznam obslužných rutin. Vzhledem k tomu, že objekt C++ zvýší počet odkazů na seznam obslužných rutin před vyvoláním události, je zaručeno, že všechny obslužné rutiny budou platné. To ale také znamená, že pokud odeberete obslužnou rutinu události v spotřebovaném vlákně, může se tato obslužná rutina stále vyvolat, pokud objekt publikování stále pracuje na jeho kopii seznamu, což je nyní zastaralé. Objekt publikování nebude mít aktualizovaný seznam, dokud se událost neaktivuje příště.
Viz také
Systém typů
Referenční zdroje k jazyku C++/CX
Referenční informace o oborech názvů