Delegaatit ja tapahtumakäsittelijät



Samankaltaiset tiedostot
Operaattorin ylikuormitus ja käyttäjän muunnokset

Attribuutit. Copyright IT Press Tämän e-kirjan kopiointi, tulostaminen ja jakeleminen eteenpäin luvatta on kielletty.

Olion elinikä. Olion luominen. Olion tuhoutuminen. Olion tuhoutuminen. Kissa rontti = null; rontti = new Kissa();

ITKP102 Ohjelmointi 1 (6 op)

Sisällys. 6. Metodit. Oliot viestivät metodeja kutsuen. Oliot viestivät metodeja kutsuen

Sisällys. Metodien kuormittaminen. Luokkametodit ja -attribuutit. Rakentajat. Metodien ja muun luokan sisällön järjestäminen. 6.2

Sisällys. Yleistä attribuuteista. Näkyvyys luokan sisällä. Tiedonkätkentä. Aksessorit. 4.2

Mikä yhteyssuhde on?

Olio-ohjelmointi Javalla

Javan perusteita. Janne Käki

JUnit ja EasyMock (TilaustenKäsittely)

4. Luokan testaus ja käyttö olion kautta 4.1

Harjoitus Olkoon olemassa luokat Lintu ja Pelikaani seuraavasti:

Ohjelmoinnin perusteet Y Python

Asynkroninen ohjelmointi.net 4.5 versiolla

1 Tehtävän kuvaus ja analysointi

Java-kielen perusteet

Oliot viestivät metodeja kutsuen

815338A Ohjelmointikielten periaatteet Harjoitus 5 Vastaukset

Sisällys. 1. Omat operaatiot. Yleistä operaatioista. Yleistä operaatioista

ITKP102 Ohjelmointi 1 (6 op)

7. Oliot ja viitteet 7.1

Web Services tietokantaohjelmoinnin perusteet

T Olio-ohjelmointi Osa 5: Periytyminen ja polymorfismi Jukka Jauhiainen OAMK Tekniikan yksikkö 2010

1. Omat operaatiot 1.1

Sisällys. 7. Oliot ja viitteet. Olion luominen. Olio Java-kielessä

Sisällys. Yleistä attribuuteista. Näkyvyys luokan sisällä ja ulkopuolelta. Attribuuttien arvojen käsittely aksessoreilla. 4.2

15. Ohjelmoinnin tekniikkaa 15.1

Ohjelmistojen mallintaminen, sekvenssikaaviot

Ohjelmassa henkilön etunimi ja sukunimi luetaan kahteen muuttujaan seuraavasti:

Vertailulauseet. Ehtolausekkeet. Vertailulauseet. Vertailulauseet. if-lauseke. if-lauseke. Javan perusteet 2004

ITKP102 Ohjelmointi 1 (6 op)

Tapahtumapohjainen ohjelmointi. Juha Järvensivu 2007

Metodit. Metodien määrittely. Metodin parametrit ja paluuarvo. Metodien suorittaminen eli kutsuminen. Metodien kuormittaminen

Ohjelmointikielet ja -paradigmat 5op. Markus Norrena

Tietokannat II -kurssin harjoitustyö

Microsoft Visual Studio 2005

Harjoitustyö: virtuaalikone

2. Olio-ohjelmoinista lyhyesti 2.1

Graafisen käyttöliittymän ohjelmointi Syksy 2013

Ohjelmoinnin jatkokurssi, kurssikoe

Ohjelmistojen mallintaminen Olioiden yhteistyö Harri Laine 1

ITKP102 Ohjelmointi 1 (6 op)

11. oppitunti III. Viittaukset. Osa. Mikä on viittaus?

Listarakenne (ArrayList-luokka)

4. Olio-ohjelmoinista lyhyesti 4.1

10 Lock Lock-lause

14. Poikkeukset 14.1

Oliosuunnitteluesimerkki: Yrityksen palkanlaskentajärjestelmä

8. Näppäimistöltä lukeminen 8.1

Lohkot. if (ehto1) { if (ehto2) { lause 1;... lause n; } } else { lause 1;... lause m; } 16.3

15. Ohjelmoinnin tekniikkaa 15.1

on ohjelmoijan itse tekemä tietotyyppi, joka kuvaa käsitettä

Ohjelmointi 2 / 2010 Välikoe / 26.3

Ohjelmointi 1 C#, kevät 2013, 2. tentti

Metodien tekeminen Javalla

Sisällys. 15. Lohkot. Lohkot. Lohkot

Ohjelmoinnin peruskurssi Y1

Toisessa viikkoharjoituksessa on tavoitteena tutustua JUnit:lla testaukseen Eclipse-ympäristössä.

Lohkot. if (ehto1) { if (ehto2) { lause 1;... lause n; } } else { lause 1;... lause m; } 15.3

Metadatan kyseleminen Reflection-metodeilla

Apuja ohjelmointiin» Yleisiä virheitä

812341A Olio-ohjelmointi Peruskäsitteet jatkoa

ITKP102 Ohjelmointi 1 (6 op), arvosteluraportti

Olio-ohjelmointi: Luokkien toteuttaminen. Jukka Juslin

YHTEYSSUHDE (assosiation)

Sisällys. 11. Javan toistorakenteet. Laskurimuuttujat. Yleistä

5 Näppäimistö. 5.1 Näppäimistön eventit

Taulukot. Jukka Harju, Jukka Juslin

Operaattoreiden ylikuormitus. Operaattoreiden kuormitus. Operaattoreiden kuormitus. Operaattoreista. Kuormituksesta

public static void main (String [] args)

Concurrency - Rinnakkaisuus. Group: 9 Joni Laine Juho Vähätalo

Sisällys. 14. Poikkeukset. Johdanto. Johdanto

Sisältö. 22. Taulukot. Yleistä. Yleistä

Ohjelmointikieli TIE Principles of Programming Languages Syksy 2017 Ryhmä 19

812347A Olio-ohjelmointi, 2015 syksy 2. vsk. X Poikkeusten käsittelystä

1.3 Lohkorakenne muodostetaan käyttämällä a) puolipistettä b) aaltosulkeita c) BEGIN ja END lausekkeita d) sisennystä

Rajapinta (interface)

1. Kun käyttäjä antaa nollan, niin ei tulosteta enää tuloa 2. Hyväksy käyttäjältä luku vain joltain tietyltä väliltä (esim tai )

Osoittimet ja taulukot

XNA grafiikka laajennus opas. Paavo Räisänen. Tämän oppaan lähdekoodit ovat ladattavissa näiden sivujen Ladattavat osiossa.

Ohjelmoinnin perusteet Y Python

P e d a c o d e ohjelmointikoulutus verkossa

Informaatioteknologian laitos Olio-ohjelmoinnin perusteet / Salo

Pong-peli, vaihe Aliohjelman tekeminen. Muilla kielillä: English Suomi. Tämä on Pong-pelin tutoriaalin osa 3/7. Tämän vaiheen aikana

Sisällys. 14. Poikkeukset. Johdanto. Johdanto

A) on käytännöllinen ohjelmointitekniikka. = laajennetaan aikaisemmin tehtyjä luokkia (uudelleenkäytettävyys)

812341A Olio-ohjelmointi, IX Olioiden välisistä yhteyksistä

Sisältö. 2. Taulukot. Yleistä. Yleistä

Java-API, rajapinnat, poikkeukset, UML,...

JReleaser Yksikkötestaus ja JUnit. Mikko Mäkelä

19. Olio-ohjelmointia Javalla 19.1

Tehtävä 1. Tietojen lisääminen, poistaminen, päivittäminen ja tulostaminen

Tech Conference Visual Studio 2015, C#6,.NET4.6. Heikki Raatikainen. #TechConfFI

8. Näppäimistöltä lukeminen 8.1

C# ja.net. Juha Järvensivu 2007

Kompositio. Mikä komposition on? Kompositio vs. yhteyssuhde Kompositio Javalla Konstruktorit set-ja get-metodit tostring-metodi Pääohjelma

Rinnakkaisuus (.NET) Juha Järvensivu 2007

Yksikkötestaus. Kattava testaus. Moduulitestaus. Ohjelman testaus. yksikkotestaus/ Seija Lahtinen

Sisällys. 12. Näppäimistöltä lukeminen. Yleistä. Yleistä

Transkriptio:

14 Delegaatit ja tapahtumakäsittelijät Toinen käyttökelpoinen keksintö C#-kielessä ovat delegaatit. Periaatteessa ne palvelevat samaa tarkoitusta kuin C++:n funktio-osoittimet. Delegaatit ovat kuitenkin tyyppiturvattuja, turvallisia hallittuja objekteja. Tämä tarkoittaa sitä, että ajonaikainen ympäristö varmistaa, että delegaatti osoittaa kelvolliseen metodiin, joka puolestaa tarkoittaa sitä, että sinulla on käytössäsi kaikki funktio-osoittimien edut ilman niihin liittyviä vaaroja, esimerkiksi virheellistä osoitetta tai vaaraa, että delegaatti vioittaisi muiden objektien muistialueita. Tässä luvussa tutkimme delegaatteja, vertaamme niitä rajapintoihin, käymme läpi delegaatin määrittelyn syntaksin ja erilaiset ongelmat, joiden ratkaisemiseksi ne on kehitetty. Näemme myös useita delegaattien käyttöesimerkkejä sekä takaisinkutsumetodeissa että asynkronisessa tapahtumakäsittelyssä. Luvussa 9, Rajapinnat, näimme, miten C#:ssa määritellään ja toteutetaan rajapintoja. Kuten muistat, käsitteellisessä mielessä rajapinnat ovat yksinkertaisesti kahden erillisen koodin välisiä sopimuksia. Rajapinnat ovat paljon luokkien kaltaisia siinä, että ne määritellään käännöksen aikana ja ne voivat sisältää metodeja, ominaisuuksia, indeksoijia ja tapahtumia. Delegaatit toisaalta viittaavat yhteen metodiin ja ne määritellään suorituksen aikana. Delegaateilla on kaksi tärkeää käyttöpaikkaa C#-ohjelmissa: takaisinkutsuissa ja tapahtumakäsittelyssä. Aloitetaan puhumalla takaisinkutsumetodeista. Delegaattien käyttö takaisinkutsumetodeina Microsoft Windows -ohjelmoinnissa laajalti käytettyjä takaisinkutsumetodeja tarvitaan, kun sinun pitää välittää funktio-osoitin toiselle funktiolle, joka sitten kutsuu takaisin ensimmäistä metodia funktio-osoittimen avulla. Esimerkkinä tästä vaikkapa Win32 APIn 281

Osa III Koodin kirjoittaminen EnumWindows-funktio. Tämä funktio luetteloi kaikki ruudulla olevat ylimmän tason ikkunat kutsuen kunkin ikkunan sisältämää funktiota. Takaisinkutsu palvelee monia tarkoituksia, joista seuraavassa on lueteltu yleisimmät: Asynkroninen käsittely Takaisinkutsumetodeja käytetään asynkronisessa käsittelyssä, kun kutsuttavan koodin suoritus kestää ajallisesti kauan. Tavallinen tilanne on tämä: Asiakasohjelma kutsuu metodia välittäen sille takaisinkutsumetodin. Kutsuttu metodi käynnistää säikeen ja palaa välittömästi sen jälkeen. Säie tekee sitten varsinaisen työn ja kutsuu takaisinkutsufunktiota tarpeen mukaan. Tästä on se ilmiselvä etu, että asiakasohjelma voi jatkaa toimintaansa eikä se jää jumiin odottamaan mahdollisesti kauan kestävän funktion suoritusta. Koodin lisääminen luokan koodiksi Toinen yleinen takaisinkutsumetodien käyttötilanne on silloin, kun luokka sallii asiakasohjelman määrittelevän metodin, jota luokka kutsuu erikoistoiminnon suorittamiseksi. Katsotaan selventävää esimerkkiä Windowsista. Käyttämällä Windowsin ListBox-luokkaa, voit määritellä, etä sen rivit lajitellaan nousevaan tai laskevaan järjestykseen. Peruslajittelun lisäksi ListBox-luokka ei anna sinulle lajittelun suhteen liikkumavaraa. Sen sijaan se antaa sinulle mahdollisuuden määritellä takaisinkutsufunktion lajittelun suorittamiseksi. Tällöin, kun ListBox tekee lajittelun, se kutsuukin takaisinkutsufunktiota ja koodisi voi silloin tehdä lajittelun omien tarpeidesi mukaan. Katsotaan nyt esimerkkiä delegaatin määrittelystä ja käytöstä. Tässä esimerkissä meillä on tietokannan hallintaluokka, joka pitää kirjaa kaikista tietokannan aktiivisista yhteyksistä ja tarjoaa metodin yhteyksien luetteloimiseksi. Oletetaan, että hallintaluokka on etäpalvelimella, jolloin on järkevää tehdä metodista asynkroninen ja sallia asiakasohjelman käyttävän takaisinkutsumetodia. Todellisessa elämässä tekisit monisäikeisen sovelluksen, jotta se olisi aito asynkroninen sovellus. Pitääksemme sovelluksen kuitenkin yksinkertaisena ja koska emme ole vielä käsitelleet monisäikeistä ohjelmointia, jätämme sen pois. Määritellään ensin kaksi pääluokkaa: DBManager ja DBConnection. class DBConnection 282

Delegaatit ja tapahtumakäsittelijät Luku 14 class DBManager static DBConnection[] activeconnections; public delegate void EnumConnectionsCallback(DBConnection connection); public static void EnumConnections(EnumConnectionsCallback callback) foreach (DBConnection connection in activeconnections) callback(connection); EnumConnectionsCallback-metodi on delegaatti ja se määritellään kirjoittamalla avainsana delegate metodin esittelyn eteen. Näet, että delegaatin paluuarvoksi on määritelty void ja että se ottaa yhden parametrin: DBConnection-objektin. EnumConnections-metodi on määritelty ottamaan ainoaksi parametrikseen EnumConnectionsCallback-metodin. Kutsuessamme DBManager.EnumConnections-metodia, meidän pitää välittää sille parametrina vain instantioitu DBManager.EnumConnectionCallback-delegaatti. Se tehdään käyttämällä new-avainsanaa välittämällä sille parametrina sen metodin nimi, jolla on sama otsikkorivi kuin delegaatilla. Tässä esimerkki: DBManager.EnumConnectionsCallback mycallback = new DBManager.EnumConnectionsCallback(ActiveConnectionsCallback); DBManager.EnumConnections(myCallback); Voit myös yhdistää nämä yhdeksi kutsuksi näin: DBManager.EnumConnections(new DBManager.EnumConnectionsCallback(ActiveConnectionsCallback)); Siinä kaikki delegaatin perusrakenteesta. Katsotaan nyt täydellistä esimerkkisovellusta: using System; class DBConnection public DBConnection(string name) this.name = name; (jatkuu) 283

Osa III Koodin kirjoittaminen protected string Name; public string name get return this.name; set this.name = value; class DBManager static DBConnection[] activeconnections; public void AddConnections() activeconnections = new DBConnection[5]; for (int i = 0; i < 5; i++) activeconnections[i] = new DBConnection( DBConnection + (i + 1)); public delegate void EnumConnectionsCallback(DBConnection connection); public static void EnumConnections(EnumConnectionsCallback callback) foreach (DBConnection connection in activeconnections) callback(connection); class Delegate1App public static void ActiveConnectionsCallback(DBConnection connection) Console.WriteLine( Callback method called for + connection.name); 284

Delegaatit ja tapahtumakäsittelijät Luku 14 public static void Main() DBManager dbmgr = new DBManager(); dbmgr.addconnections(); DBManager.EnumConnectionsCallback mycallback = new DBManager.EnumConnectionsCallback(ActiveConnectionsCallback); DBManager.EnumConnections(myCallback); Tämän sovelluksen kääntäminen ja ajaminen saa aikaan seuraavat tulokset: Callback method called for DBConnection 1 Callback method called for DBConnection 2 Callback method called for DBConnection 3 Callback method called for DBConnection 4 Callback method called for DBConnection 5 Delegaattien määritteleminen staattisiksi jäseniksi Koska on aika massiivinen operaatio, että asiakasohjelman pitää instantioida delegaatti joka kerta, kun sitä käytetään, C# antaa mahdollisuuden määritellä staattisen jäsenmetodin, jota käytetään delegaatin luontiin. Seuraavassa on edellisen kappaleen esimerkki muutettuna tähän muotoon. Huomaa, että delegaatti määritellään nyt mycallback-luokan staattiseksi jäseneksi ja huomaa myös, että tätä jäsentä voidaan käyttää Main-metodissa ilman, että asiakasohjelman pitää instantioida delegaattia. using System; class DBConnection public DBConnection(string name) this.name = name; protected string Name; public string name get return this.name; set (jatkuu) 285

Osa III Koodin kirjoittaminen this.name = value; class DBManager static DBConnection[] activeconnections; public void AddConnections() activeconnections = new DBConnection[5]; for (int i = 0; i < 5; i++) activeconnections[i] = new DBConnection( DBConnection + (i + 1)); public delegate void EnumConnectionsCallback(DBConnection connection); public static void EnumConnections(EnumConnectionsCallback callback) foreach (DBConnection connection in activeconnections) callback(connection); class Delegate2App public static DBManager.EnumConnectionsCallback mycallback = new DBManager.EnumConnectionsCallback(ActiveConnectionsCallback); public static void ActiveConnectionsCallback(DBConnection connection) Console.WriteLine ( Callback method called for + connection.name); public static void Main() DBManager dbmgr = new DBManager(); dbmgr.addconnections(); DBManager.EnumConnections(myCallback); 286

Delegaatit ja tapahtumakäsittelijät Luku 14 Huomaa Koska nimistandardi delegaateille suosittelee sanan Callback lisäämistä sen metodin nimeen, joka ottaa delegaatin parametrikseen, on helppoa epähuomiossa käyttää metodin nimeä delegaatin nimen sijasta. Silloin saat jotakuinkin hämäävän kääntäjän virheilmoituksen, joka ilmoittaa, että olet käyttänyt metodia paikassa, jossa odotetaan luokkaa. Jos saat tämän virheen, muista, että todellinen virhe on siinä, että olet määritellyt metodin delegaatin sijasta. Delegaattien luominen vain tarpeen vaatiessa Kahdessa tähän mennessä olleessa esimerkissä delegaatti on luotu, on sitä tarvittu tai ei. Se sopii näissä esimerkeissä, koska tiedän, että niitä tullaan joka kerta kutsumaan. Kun määrittelet delegaatteja, on kuitenkin tärkeää miettiä milloin ne luodaan. Sanotaan vaikkapa, että jonkun delegaatin luominen kestää kauan etkä halua tehdä sitä tarpeettomasti. Tilanteissa, joissa tiedät, että asiakasohjelma ei välttämättä kutsu takaisinkutsumetodia, voit viivyttää delegaatin luontia kunnes sitä tarvitaan sijoittamalla sen instantioinnin ominaisuuteen. Esimerkki tästä on seuraavassa, jossa olen muokannut DBManager-luokkaa siten, että se käyttää vain-luku-ominaisuutta (koska setter-metodi puuttuu) delegaatin instantiointiin. Delegaattia ei luoda ennen kuin ominaisuutta tarvitaan. using System; class DBConnection public DBConnection(string name) this.name = name; protected string Name; public string name get return this.name; set this.name = value; (jatkuu) 287

Osa III Koodin kirjoittaminen class DBManager static DBConnection[] activeconnections; public void AddConnections() activeconnections = new DBConnection[5]; for (int i = 0; i < 5; i++) activeconnections[i] = new DBConnection( DBConnection + (i + 1)); public delegate void EnumConnectionsCallback(DBConnection connection); public static void EnumConnections(EnumConnectionsCallback callback) foreach (DBConnection connection in activeconnections) callback(connection); class Delegate3App public DBManager.EnumConnectionsCallback mycallback get return new DBManager.EnumConnectionsCallback (ActiveConnectionsCallback); public static void ActiveConnectionsCallback(DBConnection connection) Console.WriteLine ( Callback method called for + connection.name); public static void Main() Delegate3App app = new Delegate3App(); 288

Delegaatit ja tapahtumakäsittelijät Luku 14 DBManager dbmgr = new DBManager(); dbmgr.addconnections(); DBManager.EnumConnections(app.myCallback); Delegaattikooste Mahdollisuus tehdä delegaattikooste (luomalla yksi delegaatti useasta) on yksi niistä ominaisuuksista, jotka eivät aluksi tunnu kovin käyttökelpoisilta, mutta jos tulet sellaista joskus tarvitsemaan, olet onnellinen, että C#-kehitystiimi ajatteli sitä. Katsotaanpa joitakin esimerkkejä, joissa delegaattikooste on tarpeellinen. Ensimmäisessä esimerkissä sinulla on hajautettu sovellus ja luokka, joka käy läpi annetun varastopaikan osia kutsuen takaisinkutsumetodia jokaisella osalla, jonka vapaa-saldo on vähemmän kuin 50. Todenmukaisemmassa sovelluksessa kaavassa tulisi ottaa huomioon myös tilauksessa ja matkalla-tilassa olevat määrät ja niin edelleen. Mutta pidetään esimerkki yksinkertaisena: jos osan vapaa-saldo on vähemmän kuin 50, tapahtuu jotain. Juju on siinä, että haluamme kaksi erilaista metodikutsua, jos osan saldo on alle rajan: haluamme tehdä tapahtumamerkinnän ja sen jälkeen lähettää sähköpostiviestin hankintapäällikölle. Katsotaan nyt, miten luot ohjelmallisesti yhden delegaattikoosteen useasta delegaatista: using System; using System.Threading; class Part public Part(string sku) this.sku = sku; Random r = new Random(DateTime.Now.Millisecond); double d = r.nextdouble() * 100; this.onhand = (int)d; protected string Sku; public string sku (jatkuu) 289

Osa III Koodin kirjoittaminen get set return this.sku; this.sku = value; protected int OnHand; public int onhand get return this.onhand; set this.onhand = value; class InventoryManager protected const int MIN_ONHAND = 50; public Part[] parts; public InventoryManager() parts = new Part[5]; for (int i = 0; i < 5; i++) Part part = new Part( Part + (i + 1)); Thread.Sleep(10); // Randomizer is seeded by time. parts[i] = part; Console.WriteLine( Adding part 0 on-hand = 1", part.sku, part.onhand); public delegate void OutOfStockExceptionMethod(Part part); public void ProcessInventory(OutOfStockExceptionMethod exception) 290

Delegaatit ja tapahtumakäsittelijät Luku 14 Console.WriteLine( \nprocessing inventory... ); foreach (Part part in parts) if (part.onhand < MIN_ONHAND) Console.WriteLine ( 0 (1) is below minimum on-hand 2", part.sku, part.onhand, MIN_ONHAND); exception(part); class CompositeDelegate1App public static void LogEvent(Part part) Console.WriteLine( \tlogging event... ); public static void EmailPurchasingMgr(Part part) Console.WriteLine( \temailing Purchasing manager... ); public static void Main() InventoryManager mgr = new InventoryManager(); InventoryManager.OutOfStockExceptionMethod LogEventCallback = new InventoryManager.OutOfStockExceptionMethod(LogEvent); InventoryManager.OutOfStockExceptionMethod EmailPurchasingMgrCallback = new InventoryManager.OutOfStockExceptionMethod(EmailPurchasingMgr); InventoryManager.OutOfStockExceptionMethod OnHandExceptionEventsCallback = EmailPurchasingMgrCallback + LogEventCallback; mgr.processinventory(onhandexceptioneventscallback); Sovelluksen ajaminen tuottaa seuraavat tulokset: 291

Osa III Koodin kirjoittaminen Adding part Part 1 on-hand = 16 Adding part Part 2 on-hand = 98 Adding part Part 3 on-hand = 65 Adding part Part 4 on-hand = 22 Adding part Part 5 on-hand = 70 Processing inventory... Part 1 (16) is below minimum on-hand 50 logging event... emailing Purchasing manager... Part 4 (22) is below minimum on-hand 50 logging event... emailing Purchasing manager... Käyttämällä kielen tätä ominaisuutta, voimme siis dynaamisesti havaita, mitkä metodit sisältävät takaisinkutsumetodin, yhdistää ne yhdeksi delegaatiksi ja välittää delegaattikooste kuin se olisi yksi delegaatti. Ajonaikainen ympäristö huolehtii automaattisesti, että metodeita kutsutaan järjestyksessä. Lisäksi voit poistaa delegaatteja delegaattikoosteesta käyttämällä minus-operaattoria. Se tosiasia, että näitä metodeja kutsutaan peräkkäin, saa kysymään tärkeän kysymyksen: miksi ei voi yksinkertaisesti ketjuttaa metodeja yhteen antamalla kunkin metodin kutsua seuraavaa? Tämän kappaleen esimerkissä, jossa meillä on siis vain kaksi metodia ja molempia kutsutaan aina parina peräkkäin, voisimme tehdäkin niin. Mutta tehdään esimerkistä hieman monimutkaisempi. Sanotaan, että meillä on useita varastopaikkoja, joista kukin määrää, mitä metodeja kutsutaan. Esimerkki Varasto1 voi olla keskusvarasto, joten haluamme tehdä tapahtumamerkinnän ja lähettää sähköpostiviestin hankintapäällikölle, kun taas muissa varastopaikoissa osan määrän ollessa alle rajan, teemme tapahtumamerkinnän ja lähetämme sähköpostiviestin tuon varaston päällikölle. Voimme helposti täyttää nämä vaatimukset luomalla dynaamisesti delegaattikoosteen, joka perustuu käsiteltävään varastopaikkaan. Ilman delegaatteja meidän pitäisi kirjoittaa metodi, joka ei pelkästään määrittelisi, mitä metodeita pitää kutsua vaan myös pitäisi kirjaa, mitä metodeja on jo kutsuttu ja mitä vielä pitää tämän kierroksen aikana kutsua. Kuten näet seuraavasta koodista, delegaattien avulla tästä monimutkaisesta operaatiosta tulee hyvin yksinkertainen. using System; class Part public Part(string sku) this.sku = sku; 292

Delegaatit ja tapahtumakäsittelijät Luku 14 Random r = new Random(DateTime.Now.Millisecond); double d = r.nextdouble() * 100; this.onhand = (int)d; protected string Sku; public string sku get return this.sku; set this.sku = value; protected int OnHand; public int onhand get return this.onhand; set this.onhand = value; class InventoryManager protected const int MIN_ONHAND = 50; public Part[] parts; public InventoryManager() parts = new Part[5]; for (int i = 0; i < 5; i++) Part part = new Part( Part + (i + 1)); parts[i] = part; (jatkuu) 293

Osa III Koodin kirjoittaminen Console.WriteLine ( Adding part 0 on-hand = 1", part.sku, part.onhand); public delegate void OutOfStockExceptionMethod(Part part); public void ProcessInventory(OutOfStockExceptionMethod exception) Console.WriteLine( \nprocessing inventory... ); foreach (Part part in parts) if (part.onhand < MIN_ONHAND) Console.WriteLine ( 0 (1) is below minimum onhand 2", part.sku, part.onhand, MIN_ONHAND); exception(part); class CompositeDelegate2App public static void LogEvent(Part part) Console.WriteLine( \tlogging event... ); public static void EmailPurchasingMgr(Part part) Console.WriteLine( \temailing Purchasing manager... ); public static void EmailStoreMgr(Part part) Console.WriteLine( \temailing store manager... ); public static void Main() InventoryManager mgr = new InventoryManager(); InventoryManager.OutOfStockExceptionMethod[] exceptionmethods = new InventoryManager.OutOfStockExceptionMethod[3]; 294

Delegaatit ja tapahtumakäsittelijät Luku 14 exceptionmethods[0] = new InventoryManager.OutOfStockExceptionMethod (LogEvent); exceptionmethods[1] = new InventoryManager.OutOfStockExceptionMethod (EmailPurchasingMgr); exceptionmethods[2] = new InventoryManager.OutOfStockExceptionMethod (EmailStoreMgr); int location = 1; InventoryManager.OutOfStockExceptionMethod compositedelegate; if (location == 2) compositedelegate = exceptionmethods[0] + exceptionmethods[1]; else compositedelegate = exceptionmethods[0] + exceptionmethods[2]; mgr.processinventory(compositedelegate); Nyt sovelluksen kääntäminen ja suorittaminen tuottaa erilaisia tuloksia riippuen arvosta, jonka annat location-muuttujalle. Tapahtumien määrittely delegaateilla Melkein kaikilla Windows-sovelluksilla on jonkinlaisia tarpeita asynkroniseen tapahtumakäsittelyyn. Jotkin näistä tapahtumista ovat yleisiä, kuten Windowsin lähettämät viestit sovelluksen viestijonoon, kun käyttäjä on ohjannut sovellusta jollakin tavalla. Jotkut ovat ongelmaläheisempiä, kuten tarve tulostaa päivitetty lasku. C#:n tapahtumat noudattavat julkaise/tilaa -menetelmää (publish-subscribe), jossa luokka julkaisee tapahtuman, jonka se voi laukaista ja joukko luokkia voi sen jälkeen tilata ilmoituksen tuosta tapahtumasta. Kun tapahtuma laukeaa, ajonaikainen ympäristö huolehtii, että kaikki sen tilanneet luokat saavat siitä ilmoituksen. Metodin, jota tapahtuman laukeamisen johdosta kutsutaan, on määritellyt delegaatti. Pidä mielessä tarkat säännöt, jotka koskevat tällä tavalla käytettävää delegaattia: Ensinnäkin, 295

Osa III Koodin kirjoittaminen delegaatti pitää määritellä ottamaan kaksi parametria. Toiseksi, nämä parametrit ovat aina kaksi objektia: objekti, joka laukaisi tapahtuman (julkaisija) ja tapahtuman informaatioobjekti. Lisäksi tämän toisen objektin tulee periytyä.net Frameworkin EventArgsluokasta. Sanotaan, että haluamme tarkkailla varastosaldojen muutoksia. Voimme luoda luokan nimeltä InventoryManager, jota käytetään aina varastosaldojen päivittämiseen. Tämä InventoryManager-luokka julkaisee tapahtuman, joka laukaistaan joka kerta, kun saldo muuttuu esimerkiksi varastokuittauksen, myynnin tai inventointipäivityksen takia. Sitten jokainen luokka, jonka pitää pysyä ajan tasalla, voi tilata tuon tapahtuman. Tämä koodataan seuraavasti C#:lla käyttäen delegaatteja ja tapahtumia: using System; class InventoryChangeEventArgs : EventArgs public InventoryChangeEventArgs(string sku, int change) this.sku = sku; this.change = change; string sku; public string Sku get return sku; int change; public int Change get return change; class InventoryManager // Publisher. public delegate void InventoryChangeEventHandler (object source, InventoryChangeEventArgs e); public event InventoryChangeEventHandler OnInventoryChangeHandler; public void UpdateInventory(string sku, int change) 296

Delegaatit ja tapahtumakäsittelijät Luku 14 if (0 == change) return; // No update on null change. // Code to update database would go here. InventoryChangeEventArgs e = new InventoryChangeEventArgs(sku, change); if (OnInventoryChangeHandler!= null) OnInventoryChangeHandler(this, e); class InventoryWatcher // Subscriber. public InventoryWatcher(InventoryManager inventorymanager) this.inventorymanager = inventorymanager; inventorymanager.oninventorychangehandler += new InventoryManager.InventoryChangeEventHandler(OnInventoryChange); void OnInventoryChange(object source, InventoryChangeEventArgs e) int change = e.change; Console.WriteLine( Part 0 was 1 by 2 units", e.sku, change > 0? increased : decreased", Math.Abs(e.Change)); InventoryManager inventorymanager; class Events1App public static void Main() InventoryManager inventorymanager = new InventoryManager(); InventoryWatcher inventorywatch = new InventoryWatcher(inventoryManager); inventorymanager.updateinventory( 111 006 116", -2); inventorymanager.updateinventory( 111 005 383", 5); Katsotaan InventoryManager-luokan kahta ensimmäistä jäsentä: 297

Osa III Koodin kirjoittaminen public delegate void InventoryChangeEventHandler (object source, InventoryChangeEventArgs e); public event InventoryChangeEventHandler OnInventoryChangeHandler; Ensimmäinen koodirivi on delegaatti, joka on, kuten nyt tiedät, metodin määrittely. Kuten aiemmin mainitsin, kaikki delegaatit, joita käytetään tapahtumissa, pitää määritellä ottamaan kaksi parametria: julkaisijaobjektin (tässä tapauksessa source) ja tapahtuman informaatioobjektin (EventArgs-objektista periytyvä). Toinen rivi käyttää event-avainsanaa, jäsentyyppiä, jolla määrittelet delegaatin, eli metodin (metodit) jota kutsutaan, kun tapahtuma laukaistaan. InventoryManager-luokan viimeinen metodi on UpdateInventory, jota kutsutaan joka kerta, kun varastosaldo muuttuu. Kuten näet, tämä metodi luo InventoryChangeEventArgstyyppisen objektin. Tämä objekti välitetään kaikille tilaajille ja sitä käytetään kuvaamaan tapahtumaa. Katsotaan nyt seuraavia koodirivejä: if (OnInventoryChangeHandler!= null) OnInventoryChangeHandler(this, e); if-käsky tarkistaa on tapahtumalla yhtään tilaajaa liitettynä OnInventoryChangeHandlermetodiin. Jos on, eli OnInventoryChangeHandler ei ole null, tapahtuma laukaistaan. Siinä todella kaikki, joka tapahtuman julkaisijan puolella pitää tehdä. Katsotaan seuraavaksi tilaajapuolen koodia. Tilaaja on tässä tapauksessa luokka InventoryWatcher. Sen pitää tehdä vain kaksi yksinkertaista asiaa. Ensiksikin sen tulee lisätä itsensä tilaajaksi instantioimalla uusi InventoryManager.InventoryChangeEventHandler-tyyppinen delegaatti ja lisäämällä sen InventoryManager.OnInventoryChangeHandler-tapahtumaan. Huomaa erityisesti käytetty syntaksi: luokka käyttää +=-yhdistelysijoitusoperaattoria lisätessään itsensä tilaajaluetteloon, jotta ei poistaisi edellisiä tilaajia. inventorymanager.oninventorychangehandler += new InventoryManager.InventoryChangeEventHandler(OnInventoryChange); Ainoa tarvittava parametri on sen metodin nimi, jota kutsutaan, jos ja kun tapahtuma laukeaa. Ainoa tehtävä, joka tilaajan tämän lisäksi pitää tehdä, on toteuttaa tapahtumakäsittelijä. Tässä tapauksessa tapahtumakäsittelijä on InventoryWatcher.OnInventoryChange, joka tulostaa viestissä osanumeron ja saldon muutoksen. Tämän sovelluksen suorittava koodi instantioi InventoryManager ja InventoryWatcherluokat. Joka kerta, kun InventoryManager.UpdateInventory-metodia kutsutaan, tapahtuma laukeaa automaattisesti ja se aiheuttaa InventoryWatcher.OnInventoryChanged-metodin kutsumisen. 298

Delegaatit ja tapahtumakäsittelijät Luku 14 Yhteenveto C#:n delegaatit ovat tyyppiturvattuja, turvallisia hallittuja objekteja, jotka palvelevat samaa tarkoitusta kuin C++:n funktio-osoittimet. Delegaatit eroavat luokista ja rajapinnoista siinä, että niitä ei määritellä käännöksen aikana vaan ohjelman suorituksen aikana ja ne viittaavat yhteen metodiin. Delegaatteja käytetään yleisesti asynkronisissa toiminnoissa ja kun pitää saada yksilöllinen toiminta asiakasluokkaan. Delegaatteja voidaan käyttää monissa tarkoituksissa, esimerkiksi takaisinkutsumetodeissa, staattisten metodien määrittelyssä tapahtumien määrittelyssä. 299