V tomto díle se zaměřím na, dle mého názoru, velmi zajímavou vlastnost jazyku C#, pomocí které je nám při vývoji aplikací umožněno tvořit objekty reprezentující reference na metody. Tyto objekty jsou v jazyce C# nazývány delegáty.
K čemu delegáty?
Jak jsem nastínil o pár řádek výše, instance delegátů slouží k reprezentaci reference na metodu. Takto referencované metody mohou být jak instanční tak statické. Pokud vytváříme typ delegáta, vytváříme vlastně předpis pro signaturu metody. Při vytváření instance delegáta předáme konstruktoru naimplementovanou metodu, která má stejnou signaturu jakou předepisuje delegát. To nám umožňuje, podobně jako u rozhraní, oddělit specifikaci od vlastní implementace.
My tedy při deklaraci delegáta tedy pouze specifikujeme požadovaný tvar a na uživateli našich knihoven je vytvoření příslušné metody požadovaného tvaru a její následné obsazení. Delegáty jsou někdy označovány jako „bezpečné ukazatele na funkce“, avšak na rozdíl od ukazatelů na funkce, známých z jazyku C++, jsou delegáty objektově orientované a typově bezpečné.
Deklarace delegáta
Chceme-li v námi vytvářeném jmenném prostoru respektive třídě, deklarovat delegáta použijeme k tomu klíčové slovo delegate.
Specifikátor_přístupu delegate návratový_typ identifikátor(seznam_formálních_parametrů)
Tímto vytvoříme nový typ delegáta. V případě, že máme v úmyslu vytvořit delegáta představující libovolnou matematickou operaci, která očekává dva parametry a její návratový typ je double uskutečníme tak tímto způsobem:
public delegate double MatematickaOperace(int a, int b);
Vytvoření instance delegáta
Instanci námi deklarovaného delegáta vytvoříme stejně jako u všech ostatních objektů pomocí operátoru new. Při vytvoření objektu delegáta jej musíme asociovat s konkrétní metodu o předepsané signatuře.
typ_delegáta identifikátor = new typ_delegáta(asociovaná_metoda);
Takže pokud bychom chtěli vytvořit objekt delegáta MatemetickaOperace musíme mít k dispozici nějakou metodu, kterou bychom s instancí delegáta asociovali. Pro naše ukázkové účely bude vhodné napsat třídu obsahující základní matematické operace.
public class MathOps
{
public static double Soucet(int a, int b)
{
return a + b;
}
public static double Rozdil(int a, int b)
{
return a - b;
}
public static double Soucin(int a, int b)
{
return a * b;
}
public static double Podil(int a, int b)
{
if (b == 0)
throw new DivideByZeroException("Pokus o deleni nulou");
return a / b;
}
}
Jak můžete pozorovat, tak všechny metody implementované ve třídě MathOps mají signaturu odpovídající delegátovy MatematickaOperace. V tuto chvíli již máme splněny podmínky potřebné k vytvoření instance delegáta. Takže vytvoření zapíšeme následujícím způsobem.
MatematickaOperace objektDelegata = new MatematickaOperace(MathOps.Soucet);
Tímto zápisem tedy došlo k vytvoření objektu delegáta MatematickaOperace. Jelikož jsou všechny metody ve třídě MathOps deklarovány jako statické, není přístupu k nim potřebné vytvořit instanci třídy MathOps. V případě, že by metody byly deklarovány jako instanční postup vytvoření delegáta by se změnil do této podoby:
MathOps instanceMathOps = new MathOps();
MatematickaOperace objektDelegata = new MatematickaOperace(instanceMathOps.Soucet);
Poznámka: Uvedený způsob přístupu k typu delegáta je možné použít pouze v případě, že je delegát deklarován na úrovni jmenného prostoru. Delegáty je možné deklarovat i na úrovni konkrétní třídy a v těchto případech je k nim přistupováno stejně jako k vnitřním třídám, tedy jako by to byly statické členy. V případě deklarace na úrovni třídy, tedy logicky nemůže být delegát označen jako statický.
Jak se nově vzniklá instance dá využít se dozvíme v následujícím odstavci.
Volání delegáta
Delegáty velmi často využijeme jako parametry nějaké metody. Metoda jednoduše očekává předání delegáta určitého typu a na nás tedy je objekt delegáta, asociovaného s nějakou metodou, předat. Mějme pro náš příklad třídu Calculator, která obsahuje metodu, jejímž parametrem je delegát typu MatematickaOperace.
public class Calculator
{
public void ProvedOperaci(int a, int b, MatemetickaOperace operace)
{
Console.WriteLine(operace(a,b));
}
}
}
Metoda ProvedOperaci tedy kromě dvou operandů očekává objekt delegáta představující Matematickou operaci. Jediné co metoda provede je, že vypíše výsledek asociované matematické operace na konzoli a to pomocí volání delegáta skrze formální parametr metody:
operace(a,b)
Jak tedy metodě předáme konkrétní matematickou operaci názorně ukazuje metoda Test ve třídě CalculatorApp, která vytvoří instanci třídy Calculator, následně je vytvořen objekt delegáta a ten je dosazen metodě ProvedOperaci.
public class CalculatorTest
{
public static void Test()
{
Calculator lCalculator = new Calculator();
//vytvoreni instance delegata a asociace s metodou
//Soucet tridy MathOps
MatematickaOperace objektDelegata = new MatematickaOperace(MathOps.Soucet);
lCalculator.ProvedOperaci(10,5,objektDelegata);
}
}
Skládání delegátů
Delegáty disponují jednou výbornou vlastností, kterou představuje možnost složit delegáta ze dvou nebo více existujících delegátů. Takto vytvořená instance delegáta svým voláním zapříčiní volání všech metod asociovaných s instancemi delegátů, z nichž byla složena. Pro složení delegátů použijeme operátor + . Skládání delegátů lze použít pouze na delegáty stejného typu. Tak jako mohou být delegáty komponovány pomocí operátoru + , mohou být i dekomponovány a to použitím operátoru - . Následující příklad ukazuje možné použití složeného delegáta.
class MultiCast
{
public delegate void PozdravDelegate(string jmeno);
public static void Ahoj(string jmeno)
{
Console.WriteLine("Zdravime " + jmeno);
}
public static void Nashledanou(string jmeno)
{
Console.WriteLine("Nashledanou " + jmeno);
}
}
public class MultiCastTest
{
public static void RunMultiCast()
{
MultiCast.PozdravDelegate del1,del2,del3,del4;
del1 = new MultiCast.PozdravDelegate(MultiCast.Ahoj);
del2 = new MultiCast.PozdravDelegate(MultiCast.Nashledanou);
//slozeni delegatu
del3 = del1 + del2;
del3("Petr");
//dekomponovani delegatu
del4 = del3 - del1;
del4("Petr");
}
}
Instanci delegáta s identifikátorem del3 jsme vytvořili kompozicí instancí delegátů del1 a del2. Tím pádem se při volání tohoto delegáta zavolají dvě metody, jedna, která byla asociována s instancí delegáta del1 (MultiCast.Ahoj) a s ní také metoda MultiCast.Nashledanou, protože byla asociována s instancí del2. Po zavolání složeného delegáta del3 vypíše tento výstup:
Zdravime Petr
Nashledanou Petr
Instance delegáta del4 byla vytvořena odebráním instance del1 od složeného delegáta del3, po této operaci tím pádem instance del4 obsahovala pouze instanci del2 a proto se nám po jejím zavolání objeví následující výstup:
Nashledanou Petr
Zdrojové kódy příkladů je možné nalézt zde.
V příštím díle se seznámíme s událostmi u kterých se nám nabyté informace o delegátech budou velmi hodit.