Friday, August 07, 2020 00:44

Cuprins >> Delegați, Expresii Lambda, Evenimente > Covarianța și Contravarianța delegaților

Covarianța și Contravarianța delegaților

Hai să vorbim despre un subiect pe care n-ar trebui să-l întâlniți în experiența voastră zilnică de programare, dar de care ar trebui să fiți totuși conștienți, dacă doriți să deveniți ingineri profesioniști: covarianța și contravarianța delegaților. Sunt termeni generali de programare, deci, îi veți întâlni și în alte limbaje de programare, nu doar C#.

Pentru început, să creăm o clasă de bază numită Baza, și o clasă derivată care o moștenește, numită Derivat:

În continuare, să declaram două metode în interiorul clasei noastre Program, care returnează tipurile Baza și Derivat:

În mod normal, nu am returna null în metodele noastre, dar din moment ce încerc doar să demonstrez un concept, și avem de-a face doar cu tipuri la timp de compilare, null va fi suficient.

În cele din urmă, să declarăm doi delegați care vor accepta de asemenea metode care returnează tipurile Baza și Derivat, și nu preiau niciun argument:

Acum, să atribuim acestor delegați, pe rând. Vom începe cu DelegatReturneazaBaza și îi vom atribui ambele metode:

În acest moment, nu veți observa nicio eroare, programul vostru va compila și va rula perfect. Să analizăm ce trebuie compilatorul să facă în acest caz: mai întâi de toate, rețineți că delegații acceptă doar metode care se potrivesc cu semnătura lor. În exemplul meu, DelegatReturneazaBaza returnează o Baza, deci, atunci când instanțiez acest delegat și îi atribui o metodă care returnează și ea o Baza, ReturneazaBaza(), există o potrivire perfectă, deoarece atât delegatul, cât și metoda atribuită acestuia, folosesc tipul Baza. Dar, observați că în a doua atribuire, ofer delegatului meu o metodă care returnează tipul Derivat, nu tipul Baza! De ce compilatorul nu generează o eroare? De ce putem atribui ReturneazaDerivat(), care returnează un tip Derivat, unui delegat care acceptă un tip Baza?

Dacă vă gândiți la asta, de fiecare dată când invocăm ReturneazaDerivat(), restricția la timpul de compilare este aceea că TREBUIE să returneze un tip Derivat, nu poate returna un object, nu poate returna un string, nu poate returna o Baza – poate returna doar un Derivat sau orice altceva moștenește din Derivat. Deci, compilatorul știe că nu există NICIO modalitate prin care metoda noastră ReturneazaDerivat() să poată încălca faptul că DelegatReturneazaBaza trebuie să returneze o Baza, deoarece un obiect Derivat ESTE o Baza. Întrucât Derivat moștenește din Baza, are toate proprietățile, metodele, câmpurile și orice altceva din Baza, deci este o Baza.

Și asta înseamnă covarianța: avem voie să returnăm obiecte care moștenesc din tipul pe care îl așteaptă semnătura de returnare a delegatului, chiar dacă, evident, acele obiecte nu sunt cele declarate în semnătura de returnare a delegatului. Putem spune că tipul de returnare între metoda ce returnează Derivat și delegatul a cărui semnătură returnează o Baza este covariant.

Pentru a rezuma covarianța: atunci când declarăm un delegat a cărui semnătură declară că returnează tipul a, avem voie să îi atribuim metode care returnează tipul a, dar și metode care returnează tipul b, dar numai dacă b este un tip derivat din a.

În această lumină nouă, să luăm cel de al doilea delegat al nostru, DelegatReturneazaDerivat, și să facem exact același lucru pe care l-am făcut cu primul delegat:

Acum, veți primi o linie de eroare sub alocarea metodei ReturneazaBaza(). Puteți deduce cu ușurință cauza, dacă vă gândiți la aceasta: delegatul DelegatReturneazaDerivat spune că va returna întotdeauna ceva foarte specific, și anume, un Derivat. Cu toate acestea, metoda ReturneazaBaza() nu este obligată să returneze ceva atât de specific ca Derivat. Poate returna o Baza sau poate returna ORICE alt tip care moștenește din Baza. Din acest motiv, vom primi o eroare de compilare. Pe de altă parte, metoda ReturneazaDerivat() returnează un Derivat, care este exact tipul pe care DelegatReturneazaDerivat îl returnează. Nu există probleme, este o potrivire perfectă.

Să vorbim acum despre contravarianță. Voi modifica delegații mei pentru a accepta tipurile Derivat și Baza ca parametri și a returna void, și, de asemenea, voi modifica metodele mele pentru a returna void și a accepta și ele tipurile Baza și Derivat ca parametri:

Și acum, să atribuim ambele metode fiecărui delegat, așa cum am făcut prima dată. Să începem cu DelegatPreiaDerivat:

În acest caz, atunci când invocăm delegatul nostru, trebuie să îi oferim un tip Derivat ca parametru, nu îi putem oferi o Baza, deoarece Baza este mai generală decât Derivat. Delegatul necesită ceva foarte specific, un tip Derivat și din această cauză, satisface cerințele ambelor metode. Dacă ne gândim la aceasta, alocarea PreiaDerivat() este o potrivire perfectă, atât delegatul cât și metoda atribuită acestuia folosesc un tip Derivat. Dar, atunci când îi oferim delegatului metoda PreiaBaza(), întrucât PreiaBaza() preia o Baza, este mai general decât ceea ce va necesita DelegatPreiaDerivat. Când invoc DelegatPreiaDerivat, nu îi pot oferi decât un Derivat ca parametru de invocare, sau orice altceva moștenește din Derivat. Aceasta înseamnă că nu există NICIO modalitate în care să ofer ceva mai general decât un Derivat și, din moment ce Derivat ESTE o Baza, îl pot transmite metodei PreiaBaza(), deoarece această metodă acceptă tipuri mai generale decât Derivat. Poate lua un Derivat, dar poate lua și alte lucruri, cum ar fi Baza, sau orice moștenire din Baza. Cu alte cuvinte, putem atribui delegatului nostru o metodă foarte relaxată, deoarece delegatul este foarte strict și nu va atribui niciodată lucruri pe care metoda nu le va accepta.

Pentru a încheia această lecție, să luăm și delegatul DelegatPreiaBaza și să-i atribuim ambele metode:

Vom primi acum o eroare de compilator sub alocarea metodei noastre PreiaDerivat(), la fel cum s-a întâmplat și în cazul covarianței. Evident, dacă delegatul acceptă o Baza ca parametru și îi atribuim o metodă care ia și ea tot o Baza, există o potrivire perfectă și totul funcționează bine. Pe de altă parte, când încercăm să atribuim PreiaDerivat() unui delegat care acceptă o Baza, acest lucru nu este posibil, deoarece delegatul așteaptă un tip mai general, iar noi îi oferim ceva foarte specific. Un Derivat poate fi doar Derivat, în timp ce o Baza poate fi orice moștenește din ea, inclusiv Derivat. În acest caz, putem spune că argumentul Baza nu este contravariant cu Derivat.

Comments

comments

Tags: , , ,

Leave a Reply



Do NOT follow this link or you will be banned from the site!