Thursday, April 18, 2024 13:47

Cuprins >> Delegați, Expresii Lambda, Evenimente > EventHandler, sender și EventArgs

EventHandler, sender și EventArgs

Ați observat probabil faptul că în aproape toate lecțiile anterioare în care am discutat despre evenimente, am folosit Action ca tipul de delegat pentru eveniment. Evident, puteți utiliza orice tip de delegat doriți, dar, în marea majoritate a cazurilor, prin convenție (și doar prin convenție!), programatorii utilizează un tip de delegat numit EventHandler. Să luăm un exemplu a modului în care am folosit evenimentele până acum:

Avem o clasă numită SemnalTren, care găzduiește un eveniment numit VinTrenuri, și o metodă publică numită DeclanseazaSemnalTren(), pe care o folosim pentru a invoca evenimentul și a apela orice metodă abonată. Acum, voi schimba tipul de delegat al evenimentului din Action în EventHandler:

Aproape nimic nu s-a schimbat, cu excepția faptului că acum compilatorul va sublinia invocarea evenimentului nostru cu o linie roșie în zig-zag, indicând o eroare: Eroare CS7036 Nu există niciun argument care să corespundă parametrului formal necesar ‘sender’ din ‘EventHandler’ (Error CS7036 There is no argument given that corresponds to the required formal parameter ‘sender’ of ‘EventHandler’). Pentru a înțelege ce înseamnă această eroare și de ce apare în primul rând, nu trebuie decât să navigăm la declarația EventHandler și să privim semnătura acestuia:

C# semnatura EventHandler

Este ușor de observat că este doar un delegat care preia două argumente, spre deosebire de Action, care nu a preia niciunul: un obiect numit sender și un EventArgs numit e; acesta este singurul motiv pentru care avem o eroare, compilatorul așteaptă să furnizăm acești doi parametri atunci când invocăm evenimentul nostru.

Primul parametru este auto-explicativ, am învățat deja despre variabilele de tip object. Dar EventArgs este un tip nou pentru noi. Să navigăm și la definiția sa:

C# semnatura EventArgs

Puteți vedea cu ușurință că această clasă este în cea mai mare parte goală: păstrează doar o referință statică la sine însăși, dar, în afară de asta, este goală. Deci, nu lăsați toate acestea să vă sperie, nu e nimic complicat, și o voi demonstra într-o clipă.

Revenind la codul nostru inițial, știm că invocarea evenimentului așteaptă doi parametri, deoarece asta așteaptă EventHandler. Primul parametru, sender, este întotdeauna obiectul care invocă evenimentul, în timp ce al doilea parametru, e, reprezintă doar câteva detalii suplimentare despre eveniment (care pot fi orice), dacă doriți să le oferiți.

Pentru a corecta eroarea, să oferim acești doi parametri. Întrucât tocmai am explicat că primul parametru este întotdeauna obiectul care invocă evenimentul, vom folosi cuvântul cheie this ca parametru sender, deoarece this se referă la instanța de clasă SemnalTren pe care o folosim în prezent, iar SemnalTren ESTE obiectul care este invocă evenimentul VinTrenuri. În ceea ce privește cel de-al doilea parametru, întrucât am spus că îl putem folosi pentru a transmite informații suplimentare, am putea trece în mod simplu doar null, deoarece în acest moment nu avem informații suplimentare pe care le-am putea trece, dar trecerea valorii null ca parametru este în general o idee foarte proastă, din diverse motive (trecerea null ca parametru NU ESTE același lucru cu a nu trece nimic, deloc). De asemenea, am putea trece o nouă instanță a EventArgs, astfel: VinTrenuri(this, new EventArgs());, dar, dacă vă amintiți, doar ce am aruncat o privire asupra clasei EventArgs și am observat că are o instanță statică la sine însăși, numită Empty, pe care o putem folosi aici, pentru a indica faptul că nu avem nicio informație suplimentară de oferit: VinTrenuri(this, EventArgs.Empty);. EventArgs.Empty este, practic, doar un nou EventArgs gol, pe care îl vom folosi.

Totuși, să folosim și acest al doilea parametru, să trecem de fapt câteva informații suplimentare, doar pentru a înțelege cum funcționează, și de ce este util. Pentru început, să creăm o enumerare numită TipSemnalTren, cu două elemente, SemnalAudio și SemnalVizual, apoi să adăugăm o proprietate de acest tip de enumerare la clasa noastră SemnalTren, numită TipSemnal:

În continuare, să declarăm câteva instanțe ale clasei SemnalTren, și să le oferim fiecăreia valori TipSemnalTren:

Apoi, avem nevoie de o metodă care să poată gestiona evenimentul, sau, după cum am învățat deja, o metodă abonat care este apelată atunci când este invocat evenimentul:

Puteți observa că spre deosebire de exemplul din lecțiile anterioare, OpresteMasina() are acum doi parametri de același tip ca ai delegatului evenimentului nostru, deoarece, așa cum am aflat, putem atribui doar metode cu aceeași semnătură cu a delegatului căruia le atribuim. Unii dintre voi ar putea observa și faptul că atunci când invocăm evenimentul VinTrenuri, trecem this ca parametru sender, iar this se referă în acest context la un tip SemnalTren. Cu toate acestea, în metoda noastră de gestionare a evenimentului, OpresteMasina(), folosim object ca tip al parametrului sender. Aceasta deoarece nu putem ști cu adevărat ce tip va fi clasa care va invoca evenimentul, așa că folosim object, deoarece object este părintele tuturor celorlalte tipuri din .NET, toate moștenesc din acesta, de aceea, chiar dacă tipul sender este object la compilare, tipul la rulare va fi convertit în SemnalTren. Din acest motiv, avem voie să convertim parametrul sender în tipul care este de fapt (dacă știm dinainte care este acest tip), în cazul nostru, SemnalTren:

Evident, putem accesa și proprietățile TipSemnal ale acestor instanțe SemnalTren, după ce subscriem metoda OpresteMasina() la evenimentul VinTrenuri al oricăreia dintre cele două instanțe SemnalTren și apelăm metoda DeclanseazaSemnalTren(), care declanșează de fapt evenimentul:

Ieșirea va arăta astfel:

Deci, aceasta ar putea fi o modalitate de utilizare a unor date arbitrare (tipul de semnal) al instanțelor noastre SemnalTren. Un alt mod în care putem face acest lucru este folosirea celui de al doilea parametru al EventHandler, despre care am spus că poate fi gol, dacă nu dorim să trecem nicio informație suplimentară (folosind EventArgs.Empty). Să presupunem totuși, doar de dragul teoriei, că dorim să transmitem aceste informații suplimentare, să zicem că vrem să oferim exact această proprietate TipSemnal a instanțelor noastre. Dacă aruncați o privire asupra semnăturii EventHandler, veți observa că acesta are două forme, una dintre ele fiind o formă generică:

C# EventHandler versiune genericăAsta înseamnă că pot modifica declarația EventHandler astfel încât să pot trece propria mea versiune de tip EventArgs, spre deosebire de utilizarea declarației implicite EventHandler, când era folosit EventArgs. Voi declara o altă clasă numită SemnalTrenEventArgs (prin convenție, numele unui tip EventArgs personalizat poartă ca sufix cuvântul EventArgs) și voi folosi această clasă ca parametru EventArgs al versiunii generice EventHandler:

Puteți observa că SemnalTrenEventArgs este doar o simplă clasă, goală deocamdată. Fiți atenți că semnătura formei generice a EventHandler pe care am arătat-o mai sus este preluată din versiunea .NET 4.5. Înainte de versiunea 4.5, semnătura sa arăta astfel:

Partea where TEventArgs : EventArgs ne obligă practic să folosim doar clase care moștenesc din EventArgs ca parametri pentru forma generică a EventHandler. Aceasta înseamnă că înainte de .NET 4.5, aș fi fost forțată să declar SemnalTrenEventArgs astfel:

În continuare, am schimbat declarația evenimentului VinTrenuri pentru a utiliza forma generică a EventHandler:

Acest lucru m-a obligat să schimb și apelul unde invoc evenimentul și să transmit un obiect SemnalTrenEventArgs ca parametru EventArgs:

În sfârșit, a trebuit să schimb și semnătura metodei de gestionare a evenimentului, pentru a accepta și ea un parametru SemnalTrenEventArgs:

Următorul pas este mutarea proprietății TipSemnal din clasa SemnalTren în clasa SemnalTrenEventArgs, deoarece nu vom accesa tipul de semnal al instanței care a declanșat evenimentul printr-o proprietate a instanței în sine, ci prin parametrul e de tip EventArgs din EventHandler:

Acum puteți observa că SemnalTren nu mai are o proprietate numită TipSemnal, deoarece această proprietate a fost mutată în SemnalTrenEventArgs:

Deoarece folosim o instanță SemnalTrenEventArgs ca tip generic pentru EventHandler, atunci când invocăm evenimentul nostru, trebuie să trecem o instanță SemnalTrenEventArgs ca parametru al invocării, și putem seta proprietatea TipSemnal pentru această instanță aici:

În cele din urmă, deoarece am trecut un parametru SemnalTrenEventArgs ca EventArgs și pentru că am setat TipSemnal pentru acel parametru, îl putem accesa acum în metoda noastră de gestionare a evenimentelor, prin intermediul parametrului e:

Desigur, am putea avea orice număr de proprietăți, metode, câmpuri, orice, în cadrul SemnalTrenEventArgs. Acest lucru ar putea fi util în unele situații, iar cel mai elocvent exemplu este atunci când utilizați evenimentul MouseMove, spre exemplu. Acest eveniment este declanșat de fiecare dată când utilizatorul mișcă mouse-ul, iar în cazul său, parametrul e (care este de tip MouseEventArgs, o formă personalizată a EventArgs, la fel ca SemnalTrenEventArgs) are o metodă foarte utilă, GetPosition(), care oferă utilizatorului coordonatele cursorului mouse-ului pe ecran, dacă are nevoie de acestea. Un alt exemplu este evenimentul KeyUp, care este declanșat de fiecare dată când utilizatorul apasă o tastă la tastatură: în acest caz, parametrul e este de tip KeyEventHandler și conține o proprietate numită Key, care spune utilizatorului ce tastă a fost apăsată.

Așadar, iată, EventArgs, în forma sa generică, ne permite să transmitem informații utile, în caz că decidem că avem nevoie de acesta, iar acesta este unul dintre motivele pentru care EventHandler este cel mai comun tip de delegat utilizat pentru evenimente.

Tags: , , , , ,

Leave a Reply



Follow the white rabbit