Použití delegátů (Průvodce programováním v C#)

Delegát je typ, který bezpečně zapouzdřuje metodu, podobně jako ukazatel funkce v jazyce C a C++. Na rozdíl od ukazatelů funkce jazyka C jsou delegáti objektově orientované, typově bezpečné a zabezpečené. Typ delegáta je definován názvem delegáta. Následující příklad deklaruje delegát s názvem Callback , který může zapouzdřovat metodu, která přebírá řetězec jako argument a vrací void:

public delegate void Callback(string message);

Delegovaný objekt je obvykle vytvořen zadáním názvu metody, kterou delegát zabalí, nebo výrazem lambda. Jakmile se delegát vytvoří instance tímto způsobem, může být vyvolána. Vyvolání delegáta volá metodu připojenou k instanci delegáta. Parametry předané volajícímu delegátu jsou předány metodě a návratová hodnota, pokud existuje, z metody se vrátí volajícímu delegátem. Příklad:

// Create a method for a delegate.
public static void DelegateMethod(string message)
{
    Console.WriteLine(message);
}
// Instantiate the delegate.
Callback handler = DelegateMethod;

// Call the delegate.
handler("Hello World");

Typy delegátů jsou odvozeny od Delegate třídy v .NET. Typy delegátů jsou zapečetěné , nelze je odvodit a není možné odvodit vlastní třídy z Delegate. Vzhledem k tomu, že instance delegáta je objekt, lze jej předat jako argument nebo přiřadit k vlastnosti. To umožňuje metodě přijmout delegáta jako parametr a volat delegáta později. To se označuje jako asynchronní zpětné volání a je běžnou metodou upozorňování volajícího, když se dokončí dlouhý proces. Pokud se delegát používá tímto způsobem, kód používající delegát nepotřebuje žádné znalosti o implementaci použité metody. Funkce jsou podobné rozhraním zapouzdření.

Dalším běžným použitím zpětných volání je definování vlastní metody porovnání a předání tohoto delegáta do metody řazení. Umožňuje, aby se kód volajícího stal součástí algoritmu řazení. Následující příklad metoda používá Del typ jako parametr:

public static void MethodWithCallback(int param1, int param2, Callback callback)
{
    callback("The number is: " + (param1 + param2).ToString());
}

Potom můžete delegáta vytvořenému výše předat této metodě:

MethodWithCallback(1, 2, handler);

a do konzoly obdrží následující výstup:

The number is: 3

Použití delegáta jako abstrakce MethodWithCallback nemusí volat přímo konzolu – nemusí být navržena s ohledem na konzolu. Co MethodWithCallback dělá, je jednoduše připravit řetězec a předat řetězec jiné metodě. To je zvláště účinné, protože delegovaná metoda může používat libovolný počet parametrů.

Pokud je delegát vytvořen tak, aby zabalil metodu instance, delegát odkazuje jak na instanci, tak na metodu. Delegát nemá žádné znalosti o typu instance kromě metody, která se zabalí, takže delegát může odkazovat na jakýkoli typ objektu, pokud existuje metoda tohoto objektu, která odpovídá podpisu delegáta. Pokud je delegát vytvořen tak, aby zabalil statickou metodu, odkazuje pouze na metodu. Vezměte v úvahu následující deklarace:

public class MethodClass
{
    public void Method1(string message) { }
    public void Method2(string message) { }
}

Společně se statickým DelegateMethod znázorněným dříve máme tři metody, které lze zabalit Del instancí.

Delegát může při vyvolání volat více než jednu metodu. Označuje se jako vícesměrové vysílání. Pokud chcete přidat další metodu do seznamu metod delegáta – seznam vyvolání – stačí přidat dva delegáty pomocí operátorů sčítání nebo sčítání přiřazení (+, nebo +=). Příklad:

var obj = new MethodClass();
Callback d1 = obj.Method1;
Callback d2 = obj.Method2;
Callback d3 = DelegateMethod;

//Both types of assignment are valid.
Callback allMethodsDelegate = d1 + d2;
allMethodsDelegate += d3;

V tomto okamžiku allMethodsDelegate obsahuje tři metody v seznamu vyvolání –Method1Method2 a DelegateMethod. Původní tři delegáti, d1a d2d3, zůstávají beze změny. Při allMethodsDelegate vyvolání jsou volány všechny tři metody v pořadí. Pokud delegát používá referenční parametry, odkaz se postupně předává každé ze tří metod a všechny změny podle jedné metody jsou viditelné pro další metodu. Pokud některá z metod vyvolá výjimku, která není zachycena v metodě, je tato výjimka předána volajícímu delegáta a nejsou volány žádné další metody v seznamu volání. Pokud má delegát návratovou hodnotu nebo parametry out, vrátí návratovou hodnotu a parametry poslední vyvoláné metody. Chcete-li odebrat metodu ze seznamu volání, použijte operátory odčítání nebo odčítání přiřazení (- nebo -=). Příklad:

//remove Method1
allMethodsDelegate -= d1;

// copy AllMethodsDelegate while removing d2
Callback oneMethodDelegate = allMethodsDelegate - d2;

Vzhledem k tomu, že typy delegátů jsou odvozeny z System.Delegate, metody a vlastnosti definované danou třídou lze volat pro delegáta. Pokud například chcete zjistit počet metod v seznamu vyvolání delegáta, můžete napsat:

int invocationCount = d1.GetInvocationList().GetLength(0);

Delegáti s více než jednou metodou ve svém seznamu vyvolání jsou odvozeni od MulticastDelegate, což je podtřída System.Delegate. Výše uvedený kód funguje v obou případech, protože obě třídy podporují GetInvocationList.

Delegáti vícesměrového vysílání se používají ve velkém rozsahu při zpracování událostí. Zdrojové objekty událostí odesílají oznámení o událostech příjemcům, které se zaregistrovaly k přijetí této události. Pokud chcete zaregistrovat událost, příjemce vytvoří metodu navrženou pro zpracování události, pak vytvoří delegáta pro tuto metodu a předá delegáta zdroji událostí. Zdroj volá delegáta, když dojde k události. Delegát pak zavolá metodu zpracování událostí příjemce a doručí data události. Typ delegáta pro danou událost je definován zdrojem události. Další informace najdete v tématu Události.

Porovnání delegátů dvou různých typů přiřazených v době kompilace způsobí chybu kompilace. Pokud jsou instance delegáta staticky typu System.Delegate, pak je porovnání povoleno, ale vrátí hodnotu false za běhu. Příklad:

delegate void Callback1();
delegate void Callback2();

static void method(Callback1 d, Callback2 e, System.Delegate f)
{
    // Compile-time error.
    //Console.WriteLine(d == e);

    // OK at compile-time. False if the run-time type of f
    // is not the same as that of d.
    Console.WriteLine(d == f);
}

Viz také