Metodele generice, ca și clasele generice, sunt metode parametrizate (tipizate) pe care le folosim atunci când nu putem specifica tipul parametrilor metodei. De asemenea, la fel ca în cazul claselor generice, înlocuirea tipurilor necunoscute cu tipuri specifice se întâmplă la apelarea metodei.
Tipizăm o metodă atunci când adăugăm <K> după nume și înainte de acolada de deschidere a metodei, unde K înlocuiește tipul care va fi folosit ulterior.
1 |
<tip_returnat><nume_metoda><K>(<parametri>) |
Putem folosi tipul necunoscut K pentru parametrii din lista de parametri a metodei (<parametri>), al cărui tip este necunoscut și de asemenea, pentru valoarea returnată sau pentru a declara variabile de tip substitut K în corpul metodei.
De exemplu, să luăm în considerare o metodă efectuează înlocuirea valorilor a două variabile:
1 2 3 4 5 6 |
public static void Inlocuieste<K>(ref K a, ref K b) { K aVechi = a; a = b; b = aVechi; } |
Aceasta este o metodă care schimbă valorile a două variabile fără a fi interesată de tipurile acestora. De aceea o definim ca o metodă generică, pentru a o putea folosi pentru toate tipurile de variabile.
Prin urmare, acum o putem folosi pentru a schimba de exemplu numere întregi și șiruri de caractere:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
int num1 = 3; int num2 = 5; Console.WriteLine("Inainte de inlocuire: {0} {1}", num1, num2); // invocare metoda cu tip int Inlocuieste<int>(ref num1, ref num2); Console.WriteLine("Dupa inlocuire: {0} {1}\n", num1, num2); string str1 = "Buna"; string str2 = "Ziua"; Console.WriteLine("Inainte de inlocuire: {0} {1}!", str1, str2); // invocare metoda cu tip string Inlocuieste<string>(ref str1, ref str2); Console.WriteLine("Dupa inlocuire: {0} {1}!", str1, str2); Console.Read(); |
La rulare, rezultatul ar fi acesta:
Analizând codul, observăm cuvântul cheie ref, despre care nu am învățat încă. Imaginați-vă pentru moment că îl folosim pentru a păstra modificările valorilor variabilelor după ce metoda respectivă își încheie execuția.
În afară de aceasta, ar trebui să știți că prin apelarea unei metode generice puteți să omiteți declararea explicită a unui anumit tip (în exemplul nostru), deoarece compilatorul îl va detecta automat, recunoscând tipul respectivilor parametri. Cu alte cuvinte, codul nostru poate fi simplificat, folosind următoarele apeluri:
1 2 |
Inlocuieste(ref num1, ref num2); // identic cu apelarea Inlocuieste<int> Inlocuieste(ref str1, ref str2); // identic cu apelarea Inlocuieste<string> |
Compilatorul va putea recunoaște tipul specific numai dacă acest tip este implicat în lista parametrilor. Compilatorul nu poate recunoaște care este tipul specific al unei metode generice numai după tipul valorii returnate sau dacă aceasta nu are parametri. În ambele cazuri, acest tip specific va trebui să fie specificat în mod explicit. În exemplul nostru, ar fi similar cu apelul metodei inițiale, sau cu adăugarea <int> sau <string>.
Metodele statice pot fi și ele tipizate, spre deosebire de proprietățile și constructorii clasei.
În lecția noastră despre clase generice am avut următorul exemplu de clasă generică cu două metode generice:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class AdapostAnimale<T> { //some code public void Adaposteste(T animalNou) { // corp metoda } public T Elibereaza(int i) { // corp metoda } } |
Dacă încercăm să reutilizăm variabila, care este folosită pentru a marca tipul necunoscut al clasei generice, de exemplu ca T, în declarația metodei generice, atunci când încercăm să compilăm clasa, vom primi un avertisment: Parametrul tip „T” are același nume ca și parametrul de tip din tipul exterior „CommonOperations <T>” (Type parameter ‘T’ has the same name as the type parameter from outer type ‘CommonOperations<T>’).
Acest lucru se întâmplă deoarece domeniul de acțiune al tipului necunoscut T, definit în declarația metodei, se suprapune domeniului de acțiune al tipului necunoscut T, în declarația de clasă:
1 2 3 4 5 6 7 8 9 10 11 |
public class OperatiiComune<T> { // CS0693 public void Inlocuieste<T>(ref T a, ref T b) { T aVechi = a; a = b; b = aVechi; } } |
Prin urmare, dacă dorim să avem un cod flexibil și metoda noastră generică să fie apelată în siguranță cu un tip specific, diferit de cel din clasa generică declarat la momentul instanțierii sale, trebuie doar să declarăm înlocuitorul tipului necunoscut În declarația metodei generice să fie diferit de parametrul pentru tipul necunoscut din declarația de clasă, după cum se arată mai jos:
1 2 3 4 5 6 7 8 9 10 |
public class OperatiiComune<T> { // Fara avertismente public void Inlocuieste<K>(ref K a, ref K b) { K aVechi = a; a = b; b = aVechi; } } |
Prin urmare, asigurați-vă întotdeauna că nu vor exista suprapuneri ale inlocuitorilor tipurilor necunoscute de metodă și de clasă.
Deci! Acum, că am învățat atât despre clase generice și metode, să luăm exemplul adăpostului nostru de animale din lecția anterioară și să îl re-codăm folosind ambele concepte noi:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
public class AdapostAnimale<T> { private const int NumarLocuriPrestabilit = 20; private T[] listaAnimale; private int locuriFolosite; public AdapostAnimale() : this(NumarLocuriPrestabilit) { } public AdapostAnimale(int numarLocuri) { this.listaAnimale = new T[numarLocuri]; this.locuriFolosite = 0; } public void Adaposteste(T animalNou) { if (this.locuriFolosite >= this.listaAnimale.Length) throw new InvalidOperationException("Adapostul este plin."); this.listaAnimale[this.locuriFolosite] = animalNou; this.locuriFolosite++; } public T Elibereaza(int index) { if (index < 0 || index >= this.locuriFolosite) throw new ArgumentOutOfRangeException("Index celula invalid: " + index); T animalEliberat = this.listaAnimale[index]; for (int i = index; i < this.locuriFolosite - 1; i++) { this.listaAnimale[i] = this.listaAnimale[i + 1]; } this.listaAnimale[this.locuriFolosite - 1] = null; this.locuriFolosite--; return animalEliberat; } } |
Primul lucru pe care îl observăm este faptul că bucata de cod de mai sus generează o eroare de compilator: Nu pot converti null în tipul parametrului ‘T’ deoarece ar putea fi un tip de valoare non-nullable. Luați în considerare utilizarea ‘default(T)’ în schimb (Cannot convert null to type parameter ‘T’ because it could be a non-nullable value type. Consider using ‘default(T)’ instead).
Eroarea se află în interiorul metodei Elibereaza() și este legată de înregistrarea unei valori nule în ultima celulă eliberată (cea mai din dreapta) din adăpost. Problema este că încercăm să folosim valoarea implicită pentru un tip de referință, dar nu știm cu siguranță dacă acest tip este un tip de referință sau unul primitiv. Prin urmare, compilatorul afișează eroarea de mai sus. Dacă tipul AdapostAnimale este instanțiat de o structură și nu de o clasă, atunci valoarea nulă nu este validă.
Pentru a rezolva această problemă, trebuie să folosim în codul nostru constructul default(T) în loc de null, care returnează valoarea implicită pentru tipul particular care va fi folosit în loc de T. După cum știm, valoarea implicită pentru tipul de referință este null, iar pentru tipurile numerice – zero. Putem face următoarea modificare:
1 2 |
//this.listaAnimale[this.locuriFolosite - 1] = null; this.listaAnimale[this.locuriFolosite - 1] = default(T); |
În cele din urmă, compilarea rulează fără probleme și clasa AdapostAnimale funcționează corect. Putem testa acest lucru după cum urmează:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
static void Main(string[] args) { AdapostAnimale<Caine> adapost = new AdapostAnimale<Caine>(); adapost.Adaposteste(new Caine()); adapost.Adaposteste(new Caine()); adapost.Adaposteste(new Caine()); Caine d = adapost.Elibereaza(1); //elibereaza al doilea caine Console.WriteLine(d); d = adapost.Elibereaza(0); // elibereaza primul caine Console.WriteLine(d); d = adapost.Elibereaza(0); // elibereaza al treilea caine Console.WriteLine(d); d = adapost.Release(0); // Exceptie: index de celula invalid (nu mai sunt caini) Console.Read(); } |
În concluzie, din ultimele două lecții: clasele generice și metodele generice sporesc reutilizarea, securitatea și performanța codului, comparativ cu alte alternative non-generice. Ca regulă generală, programatorii ar trebui să se străduiască să creeze și să folosească clase generice ori de câte ori este posibil. Cu cât sunt folosite mai multe tipuri generice, cu atât se atinge un nivel superior de abstractizare în program, iar codul sursă devine mai flexibil și reutilizabil. Cu toate acestea, ar trebui să rețineți că utilizarea excesivă a elementelor generice poate duce la o generalizare exagerată, iar codul poate deveni greu de înțeles și descifrat de către alți programatori.
Tags: clasă generică, funcții, metode, metode generice