Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
System.Delegate a „
Tento článek popisuje třídy v .NET, které podporují delegáty a jak odpovídají klíčovému slovu delegate.
Co jsou delegáti?
Delegáta si můžete představit jako způsob uložení odkazu na metodu, podobně jako způsob uložení odkazu na objekt. Stejně jako můžete předávat objekty metodám, můžete předávat odkazy na metody pomocí delegátů. To je užitečné, když chcete napsat flexibilní kód, kde různé metody mohou být "zapojeny", aby poskytovaly různá chování.
Představte si například, že máte kalkulačku, která může provádět operace se dvěma čísly. Místo pevně zakódování sčítání, odčítání, násobení a dělení do samostatných metod můžete delegáty použít k reprezentaci jakékoli operace, která přebírá dvě čísla a vrací výsledek.
Definování typů delegátů
Teď se podíváme, jak vytvořit typy delegátů pomocí klíčového delegate slova. Když definujete typ delegáta, v podstatě vytváříte šablonu, která popisuje, jaký druh metod lze v tomto delegátu uložit.
Definujete delegátní typ pomocí syntaxe, která vypadá podobně jako signatura metody, ale s klíčovým slovem delegate na začátku.
// Define a simple delegate that can point to methods taking two integers and returning an integer
public delegate int Calculator(int x, int y);
Tento Calculator delegát může obsahovat odkazy na jakoukoli metodu, která přebírá dva int parametry a vrací int.
Podívejme se na praktičtější příklad. Pokud chcete seznam seřadit, musíte algoritmus řazení sdělit, jak porovnat položky. Pojďme se podívat, jak delegáti pomáhají s metodou List.Sort() . Prvním krokem je vytvoření typu delegáta pro operaci porovnání:
// From the .NET Core library
public delegate int Comparison<in T>(T left, T right);
Tento Comparison<T> delegát může obsahovat odkazy na jakoukoli metodu, která:
- Přebírá dva parametry typu
T -
intVrátí hodnotu (obvykle -1, 0 nebo 1, která označuje "menší než", "rovná se" nebo "větší než").
Když definujete typ delegáta takto, kompilátor automaticky vygeneruje třídu odvozenou z System.Delegate, která odpovídá vašemu podepsání. Tato třída zpracovává veškerou složitost ukládání a volání odkazů na metody za vás.
Typ delegáta Comparison je obecný typ, což znamená, že může pracovat s libovolným typem T. Další informace o obecných typech naleznete v tématu Obecné třídy a metody.
Všimněte si, že i když syntaxe vypadá podobně jako deklarování proměnné, ve skutečnosti deklarujete nový typ. Můžete definovat typy delegátů uvnitř tříd, přímo uvnitř oborů názvů nebo dokonce v globálním oboru názvů.
Poznámka:
Deklarování typů delegátů (nebo jiných typů) přímo v globálním oboru názvů se nedoporučuje.
Kompilátor také generuje obslužné rutiny pro přidání a odebrání pro tento nový typ, aby klienti této třídy mohli přidávat a odebírat metody ze seznamu vyvolání instance. Kompilátor vynucuje, že podpis metody, která se přidává nebo odebírá, odpovídá podpisu použitému při deklarování typu delegáta.
Deklarace instancí delegátů
Po definování typu delegáta můžete vytvořit instance (proměnné) tohoto typu. Představte si to jako vytvoření "slotu", kde můžete uložit odkaz na metodu.
Stejně jako všechny proměnné v jazyce C# nemůžete deklarovat instance delegátů přímo v oboru názvů nebo v globálním oboru názvů.
// Inside a class definition:
public Comparison<T> comparator;
Typ této proměnné je Comparison<T> (typ delegáta, který jste definovali dříve) a název proměnné je comparator. V tomto okamžiku comparator zatím neodkazuje na žádnou metodu – je to jako prázdný slot, který čeká na vyplnění.
Delegování proměnných můžete také deklarovat jako místní proměnné nebo parametry metody, stejně jako jakýkoli jiný typ proměnné.
Vyvolání delegátů
Jakmile máte instanci delegáta, která odkazuje na metodu, můžete tuto metodu volat (vyvolat) prostřednictvím delegáta. Vyvoláte metody, které jsou v seznamu vyvolání delegáta voláním tohoto delegáta, jako by to byla metoda.
Tady je postup, jak metoda používá delegáta porovnání k určení pořadí objektů:
int result = comparator(left, right);
V tomto řádku kód vyvolá metodu připojenou k delegátu. Proměnnou delegáta považujete za název metody a zavoláte ji pomocí syntaxe volání normální metody.
Tento řádek kódu však představuje nebezpečný předpoklad: předpokládá, že cílová metoda byla přidána k objektu delegáta. Pokud nebyly přiřazeny žádné metody, výše uvedený řádek by způsobil vyvolání NullReferenceException. Vzory používané k vyřešení tohoto problému jsou propracovanější než jednoduchá kontrola null a jsou popsány dále v této sérii.
Přiřazení, přidání a odebrání cílů vyvolání
Teď víte, jak definovat typy delegátů, deklarovat instance delegátů a vyvolat delegáty. Jak ale skutečně připojíte metodu k delegátu? Tady přichází přiřazení delegáta.
Pokud chcete použít delegáta, musíte mu přiřadit metodu. Metoda, kterou přiřadíte, musí mít stejný podpis (stejné parametry a návratový typ) jako typ delegáta definuje.
Podívejme se na praktický příklad. Předpokládejme, že chcete seřadit seznam řetězců podle jejich délky. Potřebujete vytvořit metodu porovnání, která odpovídá podpisu delegáta Comparison<string> :
private static int CompareLength(string left, string right) =>
left.Length.CompareTo(right.Length);
Tato metoda přebírá dva řetězce a vrátí celé číslo označující, který řetězec je "větší" (delší v tomto případě). Metoda je deklarována jako soukromá, což je naprosto v pořádku. K jeho použití s delegátem nepotřebujete, aby byla metoda součástí vašeho veřejného rozhraní.
Teď můžete tuto metodu předat metodě List.Sort().
phrases.Sort(CompareLength);
Všimněte si, že používáte název metody bez závorek. To kompilátoru říká, aby převeďte odkaz na metodu na delegáta, který lze vyvolat později. Metoda Sort() bude volat vaši CompareLength metodu vždy, když potřebuje porovnat dva řetězce.
Explicitnější můžete být také deklarováním proměnné delegáta a přiřazením metody k ní:
Comparison<string> comparer = CompareLength;
phrases.Sort(comparer);
Oba přístupy dosáhodí totéž. První přístup je stručnější, zatímco druhý zpřístupňuje přiřazení delegáta explicitněji.
U jednoduchých metod je běžné místo definování samostatné metody používat výrazy lambda :
Comparison<string> comparer = (left, right) => left.Length.CompareTo(right.Length);
phrases.Sort(comparer);
Výrazy lambda poskytují kompaktní způsob, jak definovat jednoduché metody přímo v kódu. Použití výrazů lambda pro cíle delegáta je podrobněji popsáno v další části.
Zatím se v příkladech zobrazují delegáti s jednou cílovou metodou. Objekty delegáta však mohou podporovat seznamy volání, které mají více cílových metod připojených k jednomu objektu delegáta. Tato funkce je obzvláště užitečná pro scénáře zpracování událostí.
Třídy Delegate a MulticastDelegate
Funkce delegáta, které jste používali, jsou na pozadí založené na dvou klíčových třídách v rozhraní .NET Framework: Delegate a MulticastDelegate. S těmito třídami obvykle nepracujete přímo, ale poskytují základ, díky kterému delegáti pracují.
Třída System.Delegate a její přímá podtřída System.MulticastDelegate poskytují podporu architektury pro vytváření delegátů, registraci metod jako cílů delegáta a vyvolání všech metod registrovaných delegátem.
Tady je zajímavý detail návrhu: System.Delegate a System.MulticastDelegate nejsou samy o sobě typy delegátů, které můžete použít. Místo toho slouží jako základní třídy pro všechny konkrétní typy delegátů, které vytvoříte. Jazyk C# vám brání v přímém dědění z těchto tříd – místo toho musíte použít delegate klíčové slovo.
Když použijete delegate klíčové slovo k deklaraci typu delegáta, kompilátor jazyka C# automaticky vytvoří třídu odvozenou z MulticastDelegate vašeho konkrétního podpisu.
Proč tento návrh?
Tento návrh má své kořeny v prvním vydání jazyka C# a .NET. Návrhový tým měl několik cílů:
Zabezpečení typu: Tým chtěl zajistit, aby jazyk vynucoval zabezpečení typů při použití delegátů. To znamená, že delegáti jsou voláni se správným typem a počtem argumentů a že návratové typy jsou správně ověřeny v době kompilace.
Výkon: Díky tomu, že kompilátor generuje konkrétní třídy delegátů, které představují konkrétní podpisy metody, může modul runtime optimalizovat vyvolání delegátů.
Jednoduchost: Delegáti byli zahrnuti ve verzi 1.0 .NET, která byla před zavedením obecných typů. Návrh potřeboval pracovat v rámci omezení času.
Řešením bylo, aby kompilátor vytvořil konkrétní třídy delegáta, které odpovídají podpisům vaší metody, a zajistilo bezpečnost typů při skrytí složitosti před vámi.
Práce s metodami delegáta
I když nemůžete přímo vytvářet odvozené třídy, občas použijete metody definované v těchto Delegate třídách MulticastDelegate . Tady jsou ty nejdůležitější, o kterých byste se chtěli dozvědět:
Každý delegát, se kterým pracujete, je odvozen z MulticastDelegate. Delegát "multicast" znamená, že při volání prostřednictvím delegáta lze vyvolat více než jednu metodu. Původní návrh považoval rozdíl mezi delegáty, kteří mohli vyvolat pouze jednu metodu a delegáty, kteří by mohli vyvolat více metod. V praxi se tento rozdíl ukázal jako méně užitečný než původně, takže všichni delegáti v .NET podporují více cílových metod.
Nejčastěji používané metody při práci s delegáty jsou:
-
Invoke(): Volá všechny metody připojené k delegátu. -
BeginInvoke()/EndInvoke(): Používá se pro asynchronní vzorce vyvolání (i kdyžasync/awaitse teď upřednostňuje)
Ve většině případů nebudete tyto metody volat přímo. Místo toho použijete syntaxi volání metody pro proměnnou delegáta, jak je znázorněno v příkladech výše. Jak ale uvidíte později v této sérii, existují vzory, které s těmito metodami pracují přímo.
Shrnutí
Teď, když jste viděli, jak se syntaxe jazyka C# mapuje na podkladové třídy .NET, jste připraveni prozkoumat, jak se používají, vytvářejí a vyvolávají delegáty silného typu v složitějších scénářích.