Saturday, June 12, 2021 20:06

Cuprins >> LINQ > Metode extensie

Metode extensie

Uneori, programatorii consideră că au nevoie să adauge funcționalități noi codurilor deja existente, pentru a le îmbunătăți sau a le completa. În cazul în care respectivul codul sursă este disponibil, sarcina este simplă – trebuie doar să adauge funcționalitatea necesară și să recompileze. Cu toate acestea, deseori se vor confrunta cu situația când codul în sine nu este disponibil, cum ar fi atunci când folosesc un o referință la un assembly (fișier .dll sau .exe). În acest caz, pentru a-și atinge obiectivul, au câteva opțiuni disponibile. Una dintre ele este moștenirea, când pot moșteni pur și simplu clasa pe care doresc să o extindă și să adauge funcționalitatea necesară în clasa derivată. Aceasta are dezavantajul major de a fi dificil de implementat, datorită faptului că ar trebui să schimbe toate instanțele clasei de bază cu instanțele clasei derivate. În afară de aceasta, există întotdeauna pericolul ca clasa pe care doresc să o moștenească să fie marcată ca sealed, ceea ce înseamnă că nu poate fi moștenită.

A doua opțiune disponibilă sunt metodele extensie. Ele ne permit să extindem tipurile deja existente (clase, structuri sau interfețe) fără a fi nevoie să le schimbăm codurile sau să folosim moștenirea, chiar și atunci când tipurile existente sunt marcate ca sealed.

Metodele extensie trebuie să fie întotdeauna declarate ca statice și pot fi declarate doar în cadrul claselor statice. Dar, pentru a le vizualiza mai bine, să luăm un exemplu simplu în care acestea ar putea fi utile. Să spunem că avem aceste coduri:

Avem două instanțe simple DateTime, data și timp, pe care le afișăm la consolă. Puteți observa că în cazul data, întrucât nu declarăm niciun segment de timp, aceasta va avea valoarea implicită de 12:00 AM, astfel:

Afișarea DateTime la consolăAcum, să presupunem că dorim să combinăm cele două obiecte, astfel încât să obținem o instanță DateTime care conține partea de dată a variabilei data și partea de timp a variabilei timp. Un mod în care putem face acest lucru este prin crearea unei noi instanțe DateTime, căreia îi alocăm valorile membrilor data și timp:

Rezultatul arată astfel:

Combinarea a două variabile DateTime în C#În cazul în care aveți nevoie de această funcționalitate în mai multe locuri, puteți crea, evident, o metodă separată care returnează această funcționalitate:

Și acum, am sfârșit cu o metodă numită Combina(), care preia doi parametri de tip DateTime și returnează o nouă valoare DateTime, care reprezintă combinația dintre data și ora celor două argumente. Este chiar oarecum plăcut sintactic: Combina(data, ora), „combină data și ora”.

Dar, nu știu despre voi, însă dacă sunteți un pic ca mine, v-ar fi și mai logic sub această formă:

Evident, acest cod va genera o eroare, deoarece DateTime nu are o metodă Combina() pe care să o putem folosi:

Din nou, din punct de vedere sintactic, această variantă este la fel de plăcută: „hei, data, de ce nu te combini cu timpul?”, deci ambele variante sunt în egală măsură valide. Cu toate acestea, eu personal o prefer pe aceasta din urmă.

În acest moment, pentru a scăpa de eroarea respectivă, am explicat deja că avem două opțiuni: moștenire sau metode extensie. Însă, dacă mergem la definiția DateTime, observăm că este o structură:

Definition of DateTime in C#

Poate că nu știți, dar structurile sunt implicit declarate ca sealed, astfel încât aceasta exclude din start moștenirea. Astfel, să punem în aplicare singura variantă rămasă, o metodă de extensie.

Așa cum spuneam, metodele de extensie pot trăi doar în cadrul claselor statice, așa că trebuie să marcăm clasa Program ca statică. Am mai spus că metodele de extensie sunt întotdeauna statice, de aceea trebuie să marcăm inclusiv metoda noastră Combina() ca statică. În cele din urmă, ultimul lucru pe care trebuie să-l facem pentru a informa compilatorul că metoda noastră Combina() este o metodă de extensie, și nu doar o metodă obișnuită, este să utilizăm cuvântul cheie this în fața primului său parametru:

Motivul pentru care folosim this în fața primului parametru este acela de a arăta compilatorului tipul pe care dorim să-l extindem cu o metodă de extensie. Cu alte cuvinte, atunci când folosim this DateTime, compilatorul înțelege că metoda noastră de extensie este o metodă care va fi adăugată tipului DateTime. Dacă am fi folosit this string, am fi creat o metodă de extensie pentru clasa string, ș.a.m.d. Rețineți faptul că cuvântul cheie this trebuie întotdeauna utilizat în fața primului parametru al metodei extensie, și nu la parametrii ulteriori.

În acest moment, veți observa că eroarea din Visual Studio a dispărut. Aceasta înseamnă două lucruri: în primul rând, pare că instanța noastră data CONȚINE acum o metodă numită Combina(), pe care o putem apela prin furnizarea unui singur parametru, timp:

DateTime variantaCombinare = data.Combina(timp);

Un moment, însă! Nu am declarat metoda noastră de extensie Combina() ca având DOI parametri? De ce nu se plânge compilatorul că folosim doar unul din ei? Motivul este acela că, deoarece am folosit cuvântul cheie this, iar compilatorul știe că aceasta este o metodă de extensie, înțelege implicit și faptul că dacă folosim această metodă pe un obiect DateTime (data, în exemplul nostru), primul parametru, this DataTime, este același cu cel asupra căruia este utilizată metoda de extensie. Cu alte cuvinte, dacă folosim data.Combina(), compilatorul știe implicit că parametrul this DateTime este sinonim cu data, deoarece apelăm metoda Combina() asupra obiectului data.

Al doilea lucru pe care îl observăm este faptul că putem folosi metodele de extensie și apelându-le explicit, așa cum fac aici:

În acest caz, întrucât nu apelăm metoda Combina() pe o instanță DateTime, suntem obligați să specificăm atât primul, cât și al doilea parametru, astfel încât compilatorul să înțeleagă la ce obiect DateTime ar trebui să combine parametrul timp. Cu toate acestea, întrucât aceasta este o metodă de extensie, este nefiresc să o folosiți în acest mod și nu este considerată o bună practică.

Lucrul de luat de aici este acela că metodele de extensie sunt doar metode statice normale și pot fi utilizate ca metode statice, dar au avantajul de a ne permite, de asemenea, să le folosim ca metode de instanță a tipurilor pe care le extind.

De fapt, la nivelul MSIL (Microsoft Intermediate Language), metodele de extensie nu sunt cod valid. Compilatorul pur și simplu „decupează” instanța asupra căreia se folosește metoda de extensie și o lipește ca prim parametru al apelului metodei statice, transformând-o în mod efectiv într-un apel de metodă statică normal.

Rețineți că prin intermediul metodelor de extensie putem adăuga „metode implementate” chiar și interfețelor. Desigur, în acest punct știm cu toții că interfețele nu pot conține funcționalitate, ele sunt folosite doar pentru a defini semnături de membri, proprietăți sau metode. Nu este în întregime adevărat. Metodele de extensie pot extinde de asemenea interfețe, caz în care „adaugă” funcționalitate unei interfețe, în același mod în care o fac în cazul tipurilor concrete. Așadar, dacă vreun angajator deștept vă întreabă dacă interfețele pot conține funcționalitate, oferiți un răspuns deștept și răspundeți că pot, dar numai prin intermediul metodelor extensie.

Metodele de extensie au și câteva dezavantaje. Unul dintre ele este faptul că, evident, nu pot accesa membrii privați ai tipurilor pe care le extind. Un alt lucru este faptul că programatorii pot ajunge într-un punct în care au o mulțime de metode de extensie, doar pentru a încerca să evite moștenirea. Personal, ca programatoare profesionistă, îmi place să folosesc metode de extensie din când în când, păstrându-le doar ca un instrument folositor de avut în cutia de instrumente. Dar locul în care metodele de extensie strălucesc cu adevărat este LINQ (Language Integrated Query), despre care vom învăța în lecțiile următoare.

Tags: , , ,

Leave a Reply



Follow the white rabbit