20 delegátů
20.1 Obecné
Deklarace delegáta definuje třídu, která je odvozena z třídy System.Delegate
. Instance delegáta zapouzdřuje seznam vyvolání, což je seznam jedné nebo více metod, z nichž každá se označuje jako volatelná entita. Například metody, které lze volat, se skládá z instance a metody v této instanci. U statických metod se volatelná entita skládá pouze z metody. Vyvolání instance delegáta s odpovídající sadou argumentů způsobí vyvolání každé volatelné entity delegáta s danou sadou argumentů.
Poznámka: Zajímavou a užitečnou vlastností instance delegáta je, že neví nebo nezajímá třídy metod, které zapouzdřuje; vše, co je důležité, je, že tyto metody jsou kompatibilní (§20.4) s typem delegáta. Díky tomu jsou delegáti dokonale vhodná pro "anonymní" vyvolání. koncová poznámka
20.2 Deklarace delegátů
Delegate_declaration je type_declaration (§14.7), který deklaruje nový typ delegáta.
delegate_declaration
: attributes? delegate_modifier* 'delegate' return_type delegate_header
| attributes? delegate_modifier* 'delegate' ref_kind ref_return_type
delegate_header
;
delegate_header
: identifier '(' parameter_list? ')' ';'
| identifier variant_type_parameter_list '(' parameter_list? ')'
type_parameter_constraints_clause* ';'
;
delegate_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| unsafe_modifier // unsafe code support
;
unsafe_modifier je definován v §23.2.
Jedná se o chybu v době kompilace, aby se stejný modifikátor zobrazoval vícekrát v deklaraci delegáta.
Delegovaná deklarace, která poskytuje variant_type_parameter_list , je obecná deklarace delegáta. Kromě toho každý delegát vnořený uvnitř obecné deklarace třídy nebo obecné deklarace struktury je sám obecný deklalarací delegáta, protože argumenty typu pro obsahující typ musí být zadány k vytvoření konstruovaného typu (§8.4).
new
Modifikátor je povolen pouze u delegátů deklarovaných v jiném typu, v takovém případě určuje, že takový delegát skryje zděděného člena stejným názvem, jak je popsáno v §15.3.5.
Modifikátory public
, protected
a internal
private
řídí přístupnost typu delegáta. Některé z těchto modifikátorů mohou být povoleny v závislosti na kontextu, ve kterém se prohlášení delegáta vyskytuje, (§7.5.2).
Název typu delegáta je identifikátor.
Stejně jako u metod (§15.6.1), pokud ref
existuje, vrátí delegát hodnotu po ref; v opačném případě, pokud je void
return_type , delegát vrátí-no-hodnotu; jinak delegát vrátí hodnotu po hodnotě.
Volitelný parameter_list určuje parametry delegáta.
Return_type deklarace delegáta vracející po hodnotě nebo return-no-value určuje typ výsledku, pokud existuje, vrácený delegátem.
Ref_return_type deklarace delegáta vrácení podle odkazu určuje typ proměnné odkazované variable_reference (§9.5) vrácenou delegátem.
Volitelný variant_type_parameter_list (§18.2.3) určuje parametry typu delegáta.
Návratový typ delegáta musí být buď void
, nebo výstupní bezpečný (§18.2.3.2).
Všechny typy parametrů typu delegáta musí být vstupně-bezpečné (§18.2.3.2). Kromě toho musí být všechny typy výstupních nebo referenčních parametrů také bezpečné pro výstup.
Poznámka: Výstupní parametry musí být kvůli běžným omezením implementace bezpečné pro vstup. koncová poznámka
Kromě toho musí být každé omezení typu třídy, omezení typu rozhraní a omezení parametru typu pro všechny parametry typu delegáta vstupně-bezpečné.
Typy delegátů v jazyce C# jsou ekvivalentní názvu, nikoli strukturálně ekvivalentní.
Příklad:
delegate int D1(int i, double d); delegate int D2(int c, double d);
Typy delegátů
D1
aD2
jsou dva různé typy, takže se nedají zaměnit, navzdory jejich identickým podpisům.end example
Stejně jako u jiných obecných deklarací typu se argumenty typu zadají k vytvoření konstruovaného typu delegáta. Typy parametrů a návratový typ vytvořeného typu delegáta jsou vytvořeny nahrazením každého parametru typu v deklaraci delegáta, odpovídajícím argumentem typu konstruovaného typu delegáta.
Jediným způsobem, jak deklarovat typ delegáta, je delegate_declaration. Každý typ delegáta je referenční typ, který je odvozen z System.Delegate
. Členové vyžadovaní pro každý typ delegáta jsou podrobně popsáni v §20.3. Typy delegátů jsou implicitně sealed
, takže není možné odvodit žádný typ z typu delegáta. Nelze také deklarovat typ třídy, který není delegovaný, odvozený z System.Delegate
. System.Delegate
není sám o sobě typem delegáta; je to typ třídy, ze kterého jsou odvozeny všechny typy delegátů.
20.3 Delegáti členové
Každý typ delegáta dědí členy z Delegate
třídy, jak je popsáno v §15.3.4. Každý typ delegáta navíc poskytne ne generickou Invoke
metodu, jejíž seznam parametrů odpovídá parameter_list v deklaraci delegáta, jehož návratový typ odpovídá return_type nebo ref_return_type v deklaraci delegáta, a pro návratové delegáty, jejichž ref_kind odpovídá delegátům v prohlášení delegáta. Metoda Invoke
musí být alespoň tak přístupná jako typ delegáta. Invoke
Volání metody pro typ delegáta je sémanticky ekvivalentní použití syntaxe vyvolání delegáta (§20.6) .
Implementace mohou definovat další členy v typu delegáta.
S výjimkou instance lze jakoukoli operaci, kterou lze použít u třídy nebo instance třídy, lze použít také na delegovat třídu nebo instanci, v uvedeném pořadí. Konkrétně je možné přistupovat ke členům System.Delegate
typu prostřednictvím obvyklé syntaxe přístupu ke členům.
20.4 Kompatibilita delegátů
Metoda nebo typ M
delegáta je kompatibilní s typem D
delegáta, pokud jsou splněny všechny následující podmínky:
D
aM
mají stejný počet parametrů a každý parametr máD
stejný modifikátor podle odkazu jako odpovídající parametr vM
souboru .- Pro každý parametr hodnoty existuje převod identity (§10.2.2) nebo implicitní převod odkazu (§10.2.8) z typu
D
parametru do odpovídajícího typu parametru vM
. - Pro každý parametr podle odkazu je typ
D
parametru stejný jako typ parametru vM
. - Jedna z následujících hodnot je pravdivá:
Tato definice kompatibility umožňuje kovarianci v návratových typech a kontravariance v typech parametrů.
Příklad:
delegate int D1(int i, double d); delegate int D2(int c, double d); delegate object D3(string s); class A { public static int M1(int a, double b) {...} } class B { public static int M1(int f, double g) {...} public static void M2(int k, double l) {...} public static int M3(int g) {...} public static void M4(int g) {...} public static object M5(string s) {...} public static int[] M6(object o) {...} }
Metody
A.M1
aB.M1
jsou kompatibilní s typyD1
delegátů aD2
, protože mají stejný návratový typ a seznam parametrů. MetodyB.M2
,B.M3
aB.M4
jsou nekompatibilní s typyD1
delegátů aD2
, protože mají různé návratové typy nebo seznamy parametrů. MetodyB.M5
aB.M6
oba jsou kompatibilní s typemD3
delegáta .end example
Příklad:
delegate bool Predicate<T>(T value); class X { static bool F(int i) {...} static bool G(string s) {...} }
Metoda
X.F
je kompatibilní s typemPredicate<int>
delegáta a metodaX.G
je kompatibilní s typemPredicate<string>
delegáta .end example
Poznámka: Intuitivní význam kompatibility delegáta spočívá v tom, že metoda je kompatibilní s typem delegáta, pokud by každé vyvolání delegáta mohlo být nahrazeno vyvoláním metody bez porušení zabezpečení typu, zpracování volitelných parametrů a polí parametrů jako explicitních parametrů. Například v následujícím kódu:
delegate void Action<T>(T arg); class Test { static void Print(object value) => Console.WriteLine(value); static void Main() { Action<string> log = Print; log("text"); } }
Metoda
Action<string>
, protože jakékoli vyvoláníAction<string>
delegáta by také bylo platné vyvoláníPokud byl podpis
Print(object value, bool prependTimestamp = false)
na příklad,Action<string>
pravidly této klauzule.koncová poznámka
20.5 Vytvoření instance delegáta
Instanci delegáta vytvoří delegate_creation_expression (§12.8.16.6), převod na typ delegáta, kombinaci delegáta nebo odebrání delegáta. Nově vytvořená instance delegáta pak odkazuje na jednu nebo více z těchto možností:
- Statická metoda odkazovaná v delegate_creation_expression nebo
- Cílový objekt (který nemůže být
null
) a metodu instance odkazovanou v delegate_creation_expression nebo - Jiný delegát (§12.8.16.6).
Příklad:
delegate void D(int x); class C { public static void M1(int i) {...} public void M2(int i) {...} } class Test { static void Main() { D cd1 = new D(C.M1); // Static method C t = new C(); D cd2 = new D(t.M2); // Instance method D cd3 = new D(cd2); // Another delegate } }
end example
Sada metod zapouzdřených instancí delegáta se nazývá seznam volání. Při vytvoření instance delegáta z jedné metody zapouzdřuje tuto metodu a její seznam vyvolání obsahuje pouze jednu položku. Pokud se však zkombinují dvě instance, které nejsounull
delegáty, zřetězeny jejich seznamy vyvolání – v pořadí, v levém operandu, pak pravý operand – vytvoří nový seznam vyvolání, který obsahuje dvě nebo více položek.
Při vytvoření nového delegáta z jednoho delegáta má výsledný seznam vyvolání pouze jednu položku, což je zdrojový delegát (§12.8.16.6).
Delegáti se kombinují pomocí binárního souboru +
(§12.10.5) a +=
operátorů (§12.21.4). Delegáta lze odebrat z kombinace delegátů pomocí binárního souboru -
(§12.10.6) a -=
operátorů (§12.21.4). Delegáty lze porovnat s rovností (§12.12.9).
Příklad: Následující příklad ukazuje vytvoření instance počtu delegátů a jejich odpovídajících seznamů vyvolání:
delegate void D(int x); class C { public static void M1(int i) {...} public static void M2(int i) {...} } class Test { static void Main() { D cd1 = new D(C.M1); // M1 - one entry in invocation list D cd2 = new D(C.M2); // M2 - one entry D cd3 = cd1 + cd2; // M1 + M2 - two entries D cd4 = cd3 + cd1; // M1 + M2 + M1 - three entries D cd5 = cd4 + cd3; // M1 + M2 + M1 + M1 + M2 - five entries D td3 = new D(cd3); // [M1 + M2] - ONE entry in invocation // list, which is itself a list of two methods. D td4 = td3 + cd1; // [M1 + M2] + M1 - two entries D cd6 = cd4 - cd2; // M1 + M1 - two entries in invocation list D td6 = td4 - cd2; // [M1 + M2] + M1 - two entries in invocation list, // but still three methods called, M2 not removed. } }
Při
cd1
vytvářenícd2
instancí se každá zapouzdřuje jednou metodou. Přicd3
vytváření instancí má seznam vyvolání dvou metodM1
aM2
v daném pořadí.cd4
seznam vyvolání obsahujeM1
v tomto pořadí ,M2
aM1
, v tomto pořadí. Seznamcd5
volání obsahujeM1
,M2
,M1
,M1
, aM2
, v tomto pořadí.Při vytváření delegáta z jiného delegáta s delegate_creation_expression výsledek má vyvolání seznamu s jinou strukturou než původní, ale výsledkem jsou stejné metody, které jsou vyvolány ve stejném pořadí. Při
td3
vytvoření zecd3
seznamu vyvolání má pouze jeden člen, ale tento člen je seznam metodM1
aM2
tyto metody jsou vyvolánytd3
ve stejném pořadí, v jakém jsou vyvolánycd3
. Podobně kdyžtd4
je vytvoření instance jeho vyvolání seznam má pouze dvě položky, ale vyvolá tři metodyM1
,M2
aM1
, v tomto pořadí stejně jakocd4
.Struktura seznamu vyvolání ovlivňuje odčítání delegáta. Delegát
cd6
, vytvořený odečtenímcd2
(který vyvolá ) odcd4
(který vyvoláM2
M1
,M2
aM1
) vyvoláM1
aM1
. Delegáttd6
, vytvořený odečtenímcd2
(který vyvolá ) odtd4
(který vyvoláM1
M2
,M2
aM1
) stále vyvoláM1
,M2
aM1
, v daném pořadí, jakoM2
není jedna položka v seznamu, ale člen vnořeného seznamu. Další příklady kombinování (i odebrání) delegátů naleznete v § 20.6.end example
Po vytvoření instance instance delegáta vždy odkazuje na stejný seznam vyvolání.
Poznámka: Mějte na paměti, že když jsou dva delegáti zkombinovány nebo jeden z nich je odebrán z jiného, nové výsledky delegátů s vlastním seznamem vyvolání; seznamy vyvolání delegátů zkombinované nebo odebrané zůstanou beze změny. koncová poznámka
20.6 Vyvolání delegáta
Jazyk C# poskytuje speciální syntaxi pro vyvolání delegáta. Pokud instance beznull
delegáta, jejíž vyvolání seznam obsahuje jednu položku, je vyvolána jedna metoda se stejnými argumenty, které byla udělena, a vrátí stejnou hodnotu jako odkazovaná metoda. (Podrobné informace o vyvolání delegáta naleznete v §12.8.9.4 .) Pokud během vyvolání takového delegáta dojde k výjimce a tato výjimka není zachycena v metodě, která byla vyvolána, hledání klauzule catch výjimky pokračuje v metodě, která volala delegáta, jako kdyby tato metoda přímo volala metodu, na kterou tento delegát odkazoval.
Vyvolání instance delegáta, jejíž seznam vyvolání obsahuje více položek, pokračuje vyvoláním jednotlivých metod v seznamu volání synchronně v pořadí. Každá volaná metoda se předává stejné množině argumentů, které byly předány instanci delegáta. Pokud takové vyvolání delegáta obsahuje referenční parametry (§15.6.2.3.3), vyvolání každé metody bude mít odkaz na stejnou proměnnou; změny této proměnné jednou metodou v seznamu vyvolání budou viditelné pro metody dále v seznamu vyvolání. Pokud vyvolání delegáta obsahuje výstupní parametry nebo návratovou hodnotu, jejich konečná hodnota bude pocházet z vyvolání posledního delegáta v seznamu. Pokud během zpracování vyvolání takového delegáta dojde k výjimce a tato výjimka není zachycena v metodě, která byla vyvolána, vyhledávání klauzule catch výjimky pokračuje v metodě, která volala delegáta, a všechny metody dále v seznamu vyvolání se nevyvolají.
Pokus o vyvolání instance delegáta, jehož hodnota je null
výsledkem výjimky typu System.NullReferenceException
.
Příklad: Následující příklad ukazuje, jak vytvořit instanci, zkombinovat, odebrat a vyvolat delegáty:
delegate void D(int x); class C { public static void M1(int i) => Console.WriteLine("C.M1: " + i); public static void M2(int i) => Console.WriteLine("C.M2: " + i); public void M3(int i) => Console.WriteLine("C.M3: " + i); } class Test { static void Main() { D cd1 = new D(C.M1); cd1(-1); // call M1 D cd2 = new D(C.M2); cd2(-2); // call M2 D cd3 = cd1 + cd2; cd3(10); // call M1 then M2 cd3 += cd1; cd3(20); // call M1, M2, then M1 C c = new C(); D cd4 = new D(c.M3); cd3 += cd4; cd3(30); // call M1, M2, M1, then M3 cd3 -= cd1; // remove last M1 cd3(40); // call M1, M2, then M3 cd3 -= cd4; cd3(50); // call M1 then M2 cd3 -= cd2; cd3(60); // call M1 cd3 -= cd2; // impossible removal is benign cd3(60); // call M1 cd3 -= cd1; // invocation list is empty so cd3 is null // cd3(70); // System.NullReferenceException thrown cd3 -= cd1; // impossible removal is benign } }
Jak je znázorněno v příkazu
cd3 += cd1;
, delegát může být přítomn v seznamu vyvolání několikrát. V tomto případě se jednoduše vyvolá jednou pro každý výskyt. Při odebrání delegáta v seznamu vyvolání je poslední výskyt v seznamu vyvolání skutečně odebraný.Bezprostředně před provedením konečného příkazu
cd3 -= cd1
;, delegátcd3
odkazuje na prázdný seznam vyvolání. Pokus o odebrání delegáta z prázdného seznamu (nebo odebrání neexistujícího delegáta z neprázdného seznamu) není chyba.Výstup, který se vytvoří, je:
C.M1: -1 C.M2: -2 C.M1: 10 C.M2: 10 C.M1: 20 C.M2: 20 C.M1: 20 C.M1: 30 C.M2: 30 C.M1: 30 C.M3: 30 C.M1: 40 C.M2: 40 C.M3: 40 C.M1: 50 C.M2: 50 C.M1: 60 C.M1: 60
end example
ECMA C# draft specification