Delegaatit (Delegates)

Delegaatti

Delegaatti (delegate) on tyyppi, joka edustaa viittausta (reference) joukkoon metodeja tietyllä etukäteen määritelyillä parametreillä ja paluuarvolla. Delegaatteja käytetään välittämään metodeja argumentteina toisille metodeille. Kun delegaatti on alustettu, siihen voidaan kiinnittää mikä tahansa metodi, joka on yhteensopiva metodikutsun parametrien ja paluuarvon kanssa. Delegaattiin kiinnitetyt metodit suoritetaan delegaatin instanssilla. Delegaatit ovat siis osoittimia metodeihin, eli delegaattia kutsuttaessa kutsutaankin sitä kuuntelevia metodeja. Delegaatti voi osoittaa useampaan metodiin yhtä aikaa.
Tapahtumankäsittelijät (event handlers) ovat itseasiassa metodeja, joita kutsutaan delegaattien avulla. Esimerkiksi GUI-ohjelmoinnissa voit luoda haluamasi metodin, jota kutsutaan kun tietty tapahtuma (event) vaikkapa Click tapahtuu käyttöliittymäkontrollille.
Seuraavat kolme koodiriviä kertovat millä tavoin metodille voidaan välittää argumentteina muuttujia tai metodeja siis delegaatteja.


		 // kokonaisluvun 5 välittäminen DoSomething-metodille
		DoSomething( 5 ); 
		// viittauksen välittäminen uuteen Person-olioon  DoSomething-metodille 
		DoSomething( new Person() ); 
		// metodin laskeAlennus välittäminen DoSomething-metodille
		DoSomething( laskeAlennus ); 
	 

Delegaatin määrittely

Delegaatin esittely on kuten metodin esittely ilman toteutusta (abstrakti metodi) lisättynä delegate-avainsanalla. Seuraavalla koodesimerkissä on määritelty delegaatti ja sitten siitä on tehty instanssi muuttujaan peca.


	class MyClass {
    	// define a delegate 
    	public delegate int PerformCalculation(int x, int y);
    	// later in method - use delegate
    	PerformCalculation peca = new PerformCalculation;
	}
	

Delegaatin ominaisuudet

Delegaateilla on seuraavia ominaisuuksia:
- Delegaatit ovat kuin C++ funktiopointterit, mutta ne ovat tyyppiturvallisia.
- Delegaattien avulla voidaan välittää metodeille metodeja samalla tavalla kuin metodeille välitetään parametrejä.
- Delegaatteja voidaan "niputtaa", eli esimerkiksi yksi tapahtuma voi kutsua useita metodeja.
- Delegaattien avulla voidaan määrittele takaisinkutsu (callback)-metodeja.
- Vaikka pääsääntöisesti delegaattien metodien täytyy vastata määriteltyä tyyppiä niin .NET Framework esitteli mahdollisuuden tyyppivariaatioon, kts lisää Using Variance in Delegates.

Esimerkki: metodin asettaminen delegaatille

Allaolevassa koodissa on määritelty delegaatti CalcHandler, ja siitä luotu delegaatti sumHandler. Sille on kiinnitetty ensin metodi: MyMath.Sum ja sitten MyMath.FakeSum. Delegaatti suoritetaan vuorotellen kummallakin metodilla.


	

Esimerkki: delegaatti suorittaa monta metodia

Allaolevassa koodissa on ensin määritelty delegaatti FormatoiLuku, ja siitä luotu instanssi formatoija. Sille on kiinnitetty kaksi eri metodia: FormatoiValuutaksi ja FormatoiKolmelleDesimaalille. Tämän jälkeen formatoija-instanssille annetaan käyttäjän antama luku, ja delegaatti-instanssi suorittaa kaikki siihen liitetyt metodit.


    

Action Delegaatti (Action Delegate)

Action-delegaattia käytetään metodin välittämiseen parametrina luomattaa erikseen delegaatti-instanssia. Delegaattiin liitettävän metodin parametrien ja paluuarvon on vastattava Action-delegaatin määrittelyä.

Seuraavassa esimerkissä luodaan Action-tyyppi, johon kiinnitetään kaksi eri metodia. Action-tyyppiin kiinnitetty metodi suoriteaan listalle nimiä.

namespace JAMK.IT  {
    public class DemoAction
    {
        public static void Test_Action()
        {
            List names = new List() { "Arska", "Pena", "Camilla" };
            //use the method SanoHei (in Finnish) 
            Action myaction = new Action(Hello.SanoHei);
            names.ForEach(myaction);
            //use the method SayHello (in English) 
            myaction = new Action(Hello.SayHello);
            names.ForEach(myaction);
        }
    }
    public class Hello
        {
            public static void SayHello(string name)
            {
                Console.WriteLine($"Hello {name}, nice to see you.");
            }
            public static void SanoHei(string name)
            {
                Console.WriteLine($"Hei, mitä kuuluu {name}?");
            }
        }
    }

Anonyymit metodit ja funktiot

C#-kielen versiossa 2.0 esiteltiin konsepti anonyymit metodit (anonymous methods), joka mahdollistaa metodin määrittelyn suoraan parametrikutsussa ilman että erikseen määritellään ja nimetään metodi. C#-kielen 3.0-versiossa esiteltiin lambda expressions joka on fiksumpi tapa määrittää metodeja delegaateille "lennosta". Sekä anonyymit metodit että lambda expressions käännetään delegaattityypiksi. Yhdessä, nämä ominaisuudet tunnetaan käsitteenä anonyymit funktiot (anonymous functions).

Lambda-lausekkeet

Lambda-lauseke on lyhennetty tapa esittää anonyymi metodi.
Lambda-lauseke koostuu kolmesta osasta:
- vasemmalla ovat parametrit
keskellä lambda-operaattori (=>)

oikealla parametreille suoritettava lauseke.
Parametreja voi olla kuinka monta tahansa tai niitä ei välttämättä ole yhtään. Jos parametreja on useita, ne tulee laittaa sulkuihin

(x, y) => x + y
Jos vain yksi parametri, niin sulut voidaan jättää pois
x => x + 1
Lambda-lausekkeita käytetään esim, LINQ-kyselyissä

    List nums = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    //haetaan parilliset
    List evens = nums.Where(n => n % 2 == 0).ToList();
    

Lambda-demot

Seuraava koodi näyttää kaikki listan nimet konsolilla


	//four names to List
    List nimet = new List { "Arska", "Kalle", "Cecilia", "Jack" };
    nimet.ForEach(x => Console.WriteLine(x)); //x is a variable inside lambda

Oheinen koodinpätkässä haetaan lambda-kutsulla nimistä ensimmäinen Ja-alkuinen nimi.


	//four names to List
    List nimet = new List { "Arska", "Kalle", "Cecilia", "Jack" };
    string foo = nimet.FirstOrDefault(x => x.StartsWith("Ja"));
    Console.WriteLine(foo); //this prints Jack
	

Seuraavassa demossa haetaan lambda-kutsulla ensimmäinen Inemo-olio jonka nimi alkaa Ja:lla.


	public class TestLambda
    {
    public class Inemo {  public string Nimi { get; set; } }
    public static void TestInemoSearch()
    {
      //four Inemos to List
      List immeiset = new List { new Inemo { Nimi = "Arska Kalinen" },
        new Inemo { Nimi = "Jack Russell" },
        new Inemo { Nimi = "Cecila Camilsson" },
        new Inemo { Nimi = "Teppo Testaaja" } };
      //try to find Inemo whcih names start with Ja, if found it returns an Inemo object
      var foo = immeiset.FirstOrDefault(x => x.Nimi.StartsWith("Ja"));
      if (foo != null)
      {
        Inemo inemo = foo;
        Console.WriteLine(("Found Inemo with name:" + inemo.Nimi)); //this prints Jack Russell
      }
    }
    

Jos haluat lukea lisää niistä, niin katso: Anonymous Functions in C# Programming Guide. Tulet tutustumaan niihin myöhemmin varsinkin LINQ:n yhteydessä.

Yhteenveto

C#-kieli tarjoaa delegaatit callback-kutsujen tekemistä varten ja vähän muutakin varten. Delegaatit ovat C#:n (ja yleisemmin .NET Frameworkin) tapa välittää metodeja niin kuin parametrit välittävät tietoa eli dataa.

Lisätietoa C# Programming Guidessa:

Delegates
Anonymous Functions Lambda Expressions