10. Delegáti

V jazyce C existuje ukazatel na metodu. Obdobný mechanismus poskytuje i jazyk C#. Jde o delegáty. Oproti ukazatelům na funkce jsou však delegáti typově bezpeční. Delegáta deklarujeme pomocí klíčového slova delegate.

[ukázka kódu]
class SomeClass 
{
  public delegate void DelegatsName(int a);
}

Uvedený příklad představuje deklaraci delegáta. Nejde o členskou položku třídy, ale pouze o deklaraci typu. Chceme-li delegáta použít, musíme nejprve vytvořit metodu se stejnou signaturou (návratovým typem a stejnými parametry) a delegáta pak instanciovat. Delegáti ale nejsou jen ekvivalentem ukazatelů na metody z jazyka C. Oproti nim mají jednu další vlastnost: kompozici (composition). Delegát nemusí být interně spojen pouze s jedinou metodou, ale s celou množinou metod. K instanci delegáta lze připojit dalšího delegáta a to pomocí operátoru += nebo +. Vrací-li delegát neprázdný návratový typ, pak je vrácena hodnota posledního delegáta. Delegáta lze odebrat pomocí -= nebo -.

[ukázka kódu]
class SomeClass 
{
  public delegate void DelegatsName(int a);
  public void Method(DelegatsName d) 
  {
    d(5); // delegát je ukazatel na metodu, kterou můžeme normálně volat
  }
}

class AnotherClass 
{
  public static void StaticRealMethod(int a) 
  {
  }
  public void RealMethod(int a) 
  {
  }
  static void Main(string[] args) 
  {
    SomeClass sc = new SomeClass();
    //je vytvořena instance delegáta
    SomeClass.DelegatsName del=new SomeClass.DelegatsName(RealMethod)
    del += SomeClass.DelegatsName(StaticRealMethod)
    // delegát je předán jako argument při volání metody
    sc.Method(del);
  }
}

10.1. Anonymní metody

V kapitole si ukážeme jak lze efektivně využít anonymních metod ve spojení s návratový typem a seznamem parametrů delegáta. Tato vlastnost přibyla ve verzi jazyka 2.0.

10.1.1. Anonymní metody

V programu se občas může objevit kód, který je volán pouze prostřednictvím delegáta. Následný kód se umísťuje do pojmenovaných metod jako součást tříd nebo struktur, což se jeví jako zbytečné. Výhodnější je využít tzv. anonymních metod, které představují elegantnější řešení.

Anonymní metody umožňují psát kód delegátů přímo "in-line" . Využít je lze v místech kódu, kde jsou očekáváni delegáti, případně delegáti s argumenty. Vytváří se klíčovým slovem delegate . Připomínají lambda funkce, které známe z jazyků Python a Lisp. Lisp je jazyk pro funkcionální programování s výrazně regulární syntaxí. Syntaxe jazyka Lisp je nazývána prefixová, neboť operátor se nachází na začátku výrazu a až po něm následují operandy. Anonymní metody jsou založeny na implicitní konverzi na typ delegate. Kde překladač vyžaduje kompatibilní seznam parametrů a návratovou hodnotu.

Dvě hlavní vlastnosti anonymních metod:

  • První vlastností je možnost vypuštění seznamu parametrů při definici delegáta, pokud tento seznam nevyužíváme. Avšak jde o rozdílnou definici než u prázdného seznamu typových parametrů, který je deklarován dvěmi prázdnými kulatými závorkami '()'.

    Seznam typových parametrů je považován za kompatibilní se všemi delegáty kromě těch, kteří mají out parametry. Parametry definující typ delegáta je nutné vždy dodat, a to ve chvíli, kdy je delegát volán.

  • Druhá možnost je využití lokálních proměnných, které jsou definovány v těle metody, v níž je anonymní metoda umístěna. Z anonymní metody můžeme přistupovat k lokálním proměnným a parametrům. Takovým proměnným a parametrům říkáme vnější (outer) proměnné anonymních metod. Navíc při odkazování se na tyto vnější proměnné je nazýváme zachycené (captured). Životnost zachycených vnějších proměnných, případně parametrů, je následně prodloužena minimálně na takovou dobu, jaká je doba životnosti náležící anonymní metody.

10.1.2. Pravidla určující anonymní metodu

Seznam parametrů delegáta je kompatibilní s anonymní metodou pokud:

  1. Anonymní metoda nedefinuje seznam parametrů a delegát nemá žádné out parametry.

  2. Pro anonymní metodu definovanou společně se seznamem parametrů platí, že seznam parametrů musí přesně odpovídat parametrům delegáta. Seznam parametrů tedy musí mít stejný počet, typ a modifikátory parametrů jako parametry delegáta.

Návratový typ delegáta je kompatibilní s anonymní metodou pokud platí alespoň jedno z těchto tvrzení:

  1. Návratový typ delegáta je void a anonymní metoda neobsahuje žádné příkazy return anebo obsahuje tento příkaz bez výrazu, takže poskytuje pouze return;.

  2. Návratový typ delegáta není void a všechny výrazy v anonymní metodě vázané s příkazem return mohou být implicitně přetypovány na návratový typ delegáta.

Návratový typ a seznam parametrů delegáta musí být kompatibilní s anonymní metodou ještě předtím, než dojde k implicitní konverzi na delegátský typ delegáta.

10.1.3. Anonymní metody v příkladech

Následující příklad využívá anonymních funkcí napsaných jako "in-line". Anonymní metody jsou zde vloženy jako parametry delegátského typu Function.

Nejprve deklarace delegáta vně příslušné třídy:

[ukázka kódu]
delegate double Function(double x);
                    

Dále si vytvoříme, již uvnitř vlastní třídy, statickou metodu Apply() s jedním parametrem pro pole čísel double a druhým parametrem bude náš vytvořený delegát. Tato metoda vrátí pole reálných čísel, jejichž přepočet je dán pozdějším předpisem anonymní funkce při volání metody.

[ukázka kódu]
static double[] Apply(double[] a, Function f)
{
    double[] result = new double[a.Length];
    for (int i = 0; i < a.Length; i++)
    {
        result[i] = f(a[i]);
    }
    return result;
}
                    

Volání metody Apply() v metodě Main() by vypadalo následovně:

[ukázka kódu]
double[] a = {0.0, 0.5, 1.0};		//fill the field
double[] squares = Apply(a, delegate(double x) { return x * x; });
                    

Jak můžete vidět, druhým parametrem v metodě Apply() je ukázka "in-line" psané anonymní metody, která je kompatibilní s delegátským typem Function. Tato anonymní metoda vrací mocninu jejího argumentu. Proto výsledek volání metody Apply() je typu double[] a obsahuje mocniny hodnot v poli a.

Vytvořením další metody MultiplyAllBy() můžeme rozšířit funkčnost stávajícího programu:

[ukázka kódu]
static double[] MultiplyAllBy(double[] a, double factor)
{
    return Apply(a, delegate(double x) { return x * factor; });
}
                    

Metoda MultiplyAllBy() vrací také pole čísel typu double[], kde je každá z hodnot v poli daná argumentem factor. Tato metoda k výpočtu využívá volání předchozí anonymní metody Apply(). V metodě Apply() anonymní metoda násobí argumenty x hodnotou argumentu factor. Volání funkce MultipleAllBy(), která přenásobí prvky hodnotou 2 typu double by vypadalo takto:

[ukázka kódu]
double[] a = {0.0, 0.5, 1.0};		//naplnění pole
double[] doubles = MultiplyAllBy(a, 2.0);
                    

10.1.4. Konverze skupinových metod

V předchozí sekci jsme si říkali, že anonymní metoda může být implicitně přetypována na kompatibilní delegátský typ. C# 2.0 dovoluje stejný způsob konverze i pro skupinu metod. Dovoluje totiž explicitním konkretizacím delegátů, aby byly ve všech případech vynechány. Využitím předešlé metody Apply()

[ukázka kódu]
static double[] Apply(double[] a, Function f)
{
    double[] result = new double[a.Length];
    for (int i = 0; i < a.Length; i++)
    {
        result[i] = f(a[i]);
    }
    return result;
}
                    

mohou být tyto výrazy

[ukázka kódu]
addButton.Click += new EventHandler(AddClick);
Apply(a, new Function(Math.Sin));
                    

zapsány následovně s vynecháním zápisu instance události a delegáta

[ukázka kódu]
addButton.Click += AddClick;
Apply(a, Math.Sin);
                    

Při využití druhé kratší formy příkazu překladač sám odvodí konkretní delegátský typ. Efekt je samozřejmě u obou forem zápisu stejný. Jedná se o novou vlastnost, která je využívána i dalšího rysu jazyka C# 2.0, a to u dedukce delegátů .