Sdílet prostřednictvím


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, protecteda internalprivate ří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 voidreturn_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 a D2 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 a M mají stejný počet parametrů a každý parametr má D stejný modifikátor podle odkazu jako odpovídající parametr v Msouboru .
  • 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 v M.
  • Pro každý parametr podle odkazu je typ D parametru stejný jako typ parametru v M.
  • Jedna z následujících hodnot je pravdivá:
    • Da oba vrátí hodnotu bez hodnoty.M
    • D a M jsou vráceny po hodnotě (§15.6.1, §20.2) a identita nebo implicitní převod odkazu existuje z návratového M typu na návratový Dtyp .
    • D a M oba vrací po ref, mezi návratovým typem a návratovým typem M Dexistuje převod identity a oba mají stejnou ref_kind.

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 a B.M1 jsou kompatibilní s typy D1 delegátů a D2, protože mají stejný návratový typ a seznam parametrů. Metody B.M2, B.M3a B.M4 jsou nekompatibilní s typy D1 delegátů a D2, protože mají různé návratové typy nebo seznamy parametrů. Metody B.M5 a B.M6 oba jsou kompatibilní s typem D3delegá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 typem Predicate<int> delegáta a metoda X.G je kompatibilní s typem Predicate<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 Print je kompatibilní s typem delegáta Action<string> , protože jakékoli vyvolání Action<string> delegáta by také bylo platné vyvolání Print metody.

Pokud byl podpis Print výše uvedené metody změněn Print(object value, bool prependTimestamp = false) na příklad, Print metoda by již nebyla kompatibilní s 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ři cd3 vytváření instancí má seznam vyvolání dvou metod M1 a M2v daném pořadí. cd4seznam vyvolání obsahuje M1v tomto pořadí , M2a M1, v tomto pořadí. Seznam cd5volání obsahuje M1, M2, M1, M1, a M2, 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í ze cd3 seznamu vyvolání má pouze jeden člen, ale tento člen je seznam metod M1 a M2 tyto metody jsou vyvolány td3 ve stejném pořadí, v jakém jsou vyvolány cd3. Podobně když td4 je vytvoření instance jeho vyvolání seznam má pouze dvě položky, ale vyvolá tři metody M1, M2a M1, v tomto pořadí stejně jako cd4 .

Struktura seznamu vyvolání ovlivňuje odčítání delegáta. Delegát cd6, vytvořený odečtením cd2 (který vyvolá ) od cd4 (který vyvolá M2M1, M2a M1) vyvolá M1 a M1. Delegát td6, vytvořený odečtením cd2 (který vyvolá ) od td4 (který vyvolá M1M2, M2a M1) stále vyvolá M1, M2 a M1, v daném pořadí, jako M2 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át cd3 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