Anda di halaman 1dari 16

Fundamentals of C# 2010

Fundamentals of C# 2010

Sumar

1. Mostenirea................................................................................................................................................... 3
1.1. Apelul constructorilor........................................................................................................................... 4
1.2. Atribuirea intre obiecte ........................................................................................................................ 5
1.3. Metode virtuale ................................................................................................................................... 6
1.4. Metode suprascrise .............................................................................................................................. 6
1.5. Metode virtuale si Polimorfismul ......................................................................................................... 7
2. Interfete si clase abstracte........................................................................................................................... 8
2.1. Interfete ............................................................................................................................................... 8
2.2. Clase abstracte................................................................................................................................... 10
3. Proprietati .................................................................................................................................................. 11
4. Tipul generic............................................................................................................................................... 13
5. Supraincarcarea operatorilor..................................................................................................................... 14

2
Fundamentals of C# 2010

1. Mostenirea
1.1. Notiuni introductive

Conceputul de mostenire (inheritance) in programare se refera la clasificare. Am discutat la


inceput despre clase ca ele sunt inspirate de fapt din lumea reala si ca prin intermediul claselor pot
defini multimi de obiecte. Pentru explicarea conceptului de mostenire plecam tot de la un exemplu
din lumea reala clasa mamiferelor. Daca luam doi reprezentati ai acestei clase, Calul si Balena,
putem gasi trasaturi comune precum : respira, se hranesc, au sange cald. Dar in acelasi timp au si
trasaturi, caracteristici proprii : calul alearga, balena inoata, calul are copite, balena are aripioare
etc. Cum am putea modela un cal si o balena intr-o aplicatie? Pai in primul rand ar trebui doua clase
distincte: clasa Cal si clasa Balena. In fiecare clasa se pot implementa comportamentele unice
pentru tipul de animal descris (pentru Cal galopul, pentru Balena inotul). Dar cum s-ar putea
modela proprietatile comune celor doua animale : respiratia, hranitul? Am putea adauga anumite
proprietati in cele doua clase, proprietati care pana la urma sunt la fel. Deci ele s-ar dublica, ceea ce
ar ingreuna destul de mult dezvoltarea (adaugarea de comportamente noi, viitoare) celor doua
clase. Solutia cea mai eleganta este mostenirea. Calul, balena, omul etc sunt pana la urma toate
mamifere. Deci vom crea o clasa comuna Mamifere in care vor fi implementate functionalitati
comune mamiferelor. Apoi, vom crea clase pentru fiecare tip de animal (clasa Cal, clasa Balena etc)
derivate din clasa parinte Mamifere. Aceste clase derivate vor avea in consecinta caracteristicile
(comportamentele) comune descrise in clasa Mamifer, dar lor se pot adauga foarte bine
caracteristici suplimentare, specifice fiecarui tip de animal descris. Daca se doreste modificarea
(sau adaugarea) unei caracteristici comune, se va actiona doar in clasa de baza Mamifer, toate
celelalte clase derivate din ea preluand automat modificarile. Mergand mai departe cu aceasta
gandire, si clasa Mamifere poate fi considerata ca derivata dintr-o clasa parinte, mai cuprinzatoare
clasa animalelor. Mergand tot mai departe cu aceasta ierarhizare, ajungem la o clasa de baza,
comuna absolut tuturor claselor. In C#, aceasta clasa este System.Object (deci in C# totul este un
obiect!)

Declararea unei clase derivate dintr-o alta clasa (parinte) se face cu sintxa:
class ClasaDerivata : ClassaParinte
{
//campuri specifice clasei derivate
}

In C# este permis ca o clasa sa fie derivata din cel mult o alta clasa. Nu se poate ca o clasa sa fie
derivata din mai mult de o clasa de baza.
Daca ne referim la exemplul de la inceputul capitolului, atunci putem defini clasele Mamifer, Cal si
Balena astfel :
class Mamifer
{
public void Respira()
{
3
Fundamentals of C# 2010

public void SeHraneste()


{
}
}

class Cal : Mamifer


{

public void Galopeaza()
{
}
}

class Balena : Mamifer


{
public void Inoata()
{

}
}

Toate clasele din C# sunt derivate din clasa radacina parinte System.Object. Deci, pentru clasa
Mamifer pe care am definit-o, compilatorul va adauga :
class Mamifer : System.Object
{
....................
}

1.2. Apelul constructorilor


Deci, prin mostenire, clasa derivata va contine automat toate campurile clasei de baza. Aceste
campuri, de cele mai multe ori, trebuie sa fie initializate. Aceasta initialziare este facuta de obicei in
constructorul clasei. De aici rezulta ca este de preferat ca in constructotul clasei derivate sa se
apeleze constructotul clasei de baza (ne amintim ca acest constructor exista, chiar daca nu e
specificat explicit; compilatorul va construi unul implicit). Pentru a apela constructorul clasei de baza
se foloseste cuvantul-cheie base atunci cand se construieste contructorul clasei derivate :
class Mamifer
{
public Mamifer(string nume) //constructorul clasei de baza
{

}

}

class Cal : Mamifer


{
public Cal(string nume)
4
Fundamentals of C# 2010

: base(nume) //se apeleaza Mamifer(nume)


{

public void Galopeaza()


{

}

}

1.3. Atribuirea intre obiecte

Asa cum amvazut in capitolele anterioare, este posibil sa facem atribuiri intre obiecte, dar
respectand anumite reguli. In cazul in care avem obiecte ale unor clase derivate din clase parinte,
vom utiliza exemplul celor trei clase de mai sus pentru exemplificare :
class Mamifer
{

}

class Cal : Mamifer


{

}

class Balena : Mamifer


{

}

Cal horse = new Cal("Cezar");


Balena whale = horse; //eroare : balena nu este un cal !

Pe de alta parte, insa, este posibil sa facem referinta la un obiect dintr-o variabla de un alt tip atat
timp cat tipul utilizat este o clasa situata mai sus in ierarhia data de mostenirea claselor. Deci,
urmatoarea atribuire este corecta :
Cal horse = new Cal("Cezar");
Mamifer m = horse; //corect

Aceasta atribuire este corecta intrucat toti Caii sunt Mamifere, deci atribuirea unui obiect de tip Cal
unui obiect de tip Mamifer este logica. Aceasta ierarhizare prin derivare are semnificatia ca un Cal
este pur si simplu un tip special de Mamifer. Desi exemplul de mai sus este perfect corect, atunci
cand fac referire la un obiect de tip Cal sau Balena printr-un obiect de tip Mamifer, pot accesa
numai campurile si metodele definite in clasa Mamifer. Campurile si metodele definite la nivelul
claselo Cal sau Balena nu vor fi visibile prin intermediul clasei Mamifer.
Cal horse = new Cal("Cezar");
5
Fundamentals of C# 2010

Mamifer m = horse; //corect


m.Respira(); //corect
m.Galopeaza(); //INCORECT

Atribuirea in sens invers decat exemplul de mai sus (adica horse = m) nu se poate face direct. Am
spus ca toti Caii sunt Mamifere, dar nu toate Mamiderele sunt Cai (unele mai sunt si Balene). Pot
face aceasta atribuire atata timp cat m a fost incarcat inainte cu o referinta la un obiect de tip Cal.

1.4. Metode virtuale

Uneori se doreste sa se ascunda modul in care este implementata o metoda in clasa de baza.
Sa luam de exemplu metoda ToString() din clasa System.Object. Scopul acestei metode este sa
converteasca un obiect in reprezentarea sa ca string. Pentru ca aceasta metoda este foarte utila, ea
a fost plasata in clasa cea mai de sus din ierarhia claselor, System.Object. Prin urmare, toate
clasele au acces la aceasta metoda (pentru ca toate clasele sunt derivate din System.Object). Si
totusi, cum stie metoda ToString() definita in System.Object sa transforme o instanta a clasei
derivate in string? Raspunsul este ca implementarea metodei in clasa System.Object este una
foarte simplista. Tot ceea ce face este sa intoarca un string care contine de fapt tipul obiectului.
Deci, se pare ca nu este prea utila in forma asta. Mai este metoda ToString() utila, fiind atat de
simpla? Raspunsul este da. Sa explicam de ce.
Din clasa de baza System.Object se pot deriva clase atat de variate, incat ar fi imposibil ca doar
implementarea din System.Object a metodei ToString() sa poate face conversia oricarei clase in
string. Pentru ca aceasta metoda sa-si indeplineasca scopul, ar trebui sa in fiecare clasa derivata
din System.Object (si cum toate sunt derivate, deci in toate clasele existente), sa implementam
clasa ToString() conform propriilor necesitati, specifice fiecarei clase in parte. Deci, practic, trebuie
sa suprascriem metoda ToString(). O metoda care este propusa pentru a fi suprascrisa (overridden)
se numeste metoda virtuala. Suprascrierea unei metoda este mecanismul prin care se dau
implementari diferite aceleasi metode, functie de logica clasei in care implementez metode, deci in
maniere diferite. Pentru a marca o metoda ca fiind virtuala, se utilizeaza cuvantul-cheie virtual in
declararea metodei :
public class Object
{
........
public virtual string ToString();
}

1.5. Metode suprascrise

Daca intr-o clasa de baza o metda este declarata ca virtuala, in clasa derivata se poate folosi
cuvantul-cheie override pentru a declara o alta implementare a acelei metode. Noua implementare
a metodei in clasa derivata poate apela metoda din clasa de baza folosind cuvantul-cheie base:

6
Fundamentals of C# 2010

class Mamifer : System.Object


{

public override string ToString()
{
return "Sunt un mamifer.";
}
}

class Cal : Mamifer


{

public override string ToString()


{
return base.ToString() + " Dar sunt si un cal.";
}
}

Exista cateva reguli referitoare la metode virtuale si suprascrise:


- Cand se utilizeaza virtual sau override, este interzis se se declare metoda ca private;
- Semnatura celor doua metode (cea declara ca virtuala in clasa de baza si cea suprascrisa
in clasa derivata) trebuie sa fie identice; in plus, trebuie sa returneze si acelasi tip;
- Ambele metode trebuie sa aiba acelasi specificator de acces;
- Se poate suprascrie numai o metoda virtuala. Daca in clasa de baza metoda nu este
declara ca virtuala, dar in clasa derivata se incearca suprascrierea (cu override), se obtine o
eroare;
- Daca in clasa derivata se declara o metoda cu acceasi semnatura ca una virtuala din clasa
de baza, dar in clasa derivata nu este specificat override, atunci se considera ca in clasa
derivata s-a declarat pur si simplu o metoda proprie, intamplator cu acelasi nume cu a celei
din clasa de baza; in acest caz, metoda din clasa de baza este ascunsa de catre cea din
clasa derivata;
- O metoda override este implicit si virtuala, si prin urmare poate fi ea insasi suprascrisa in
alte clase derivate.

1.6. Metode virtuale si Polimorfismul

Fenomenul precizat in capitolul precedent, de a declara o metoda virtuala intr-o clasa de baza si
apoi de a putea sa o rescriem in clase diferite, face ca metoda rescrisa sa aiba rezultate diferite, in
functie de implementarea din fiecare clasa derivata. Acest fenomen se numeste polimorfism. Deci
pentru a realiza acest comportament polimorfic, este necesar mecanismul de clase virtuale si
suprascrise.

7
Fundamentals of C# 2010

2. Interfete si clase abstracte

Derivarea dintr-o clasa, prin mostenire, este un mecanism puternic, dar adevarata putere a
mostenirii vine cand cea derivata este o interfata . O interfata nu contine nicio implementare si nicio
data; ea specifica doar metodele si proprietatile pe care clasa derivata din interfata trebuie sa le
ofere, sa le implementeze. Interfata permite o delimitare totala a numelui si semnaturii unei metode
de implementarea ei.
Clasele abstracte sunt similare interfetelor, doar ca ele pot contine cod si date. Totusi se pot
specifica ca anumite metode ale unei clase abstracte sunt virtuale, astfel incat o clasa care
mosteneste o astfel de clasa abstracta trebuie sa ofere propria implementare a acestor metode.

2.1. Interfete

Sa presupune ca dorim crearea unei noi clase colectie care permite unei aplicatii sa retina
obiecte intr-o ordine care depinde de tipul obiectelor pe care le contine colectia (colectia este o
multime de obiecte, de orice tip; pentru o colectie definita de la inceput ca avand elemente de un
anumit tip, atunci toate elementele din colectie sunt de tipul respectiv). De exemplu, daca colectia
contine obiecte alfanumerice precum stringurile, atunci colectia ar trebui sa ordoneze obiectele in
ordine alfabetica, iar daca colectia contine obiecte de tipul integer de exemplu, atunci colectia sa
ordoneze aceste obiecte dupa un criteriu numeric.
Cand se defineste clasa colectie, nu se doreste sa se restrictioneze tipul de obiecte care se retin
(obiectele pot fi chiar de tip structura sau clasa), si prin urmare nu cunoastem de la inceput cum sa
ordonam aceste obiecte. Intrebarea care se pune este cum putem crea o metoda in clasa colectie
care sa ordoneze obiecte pentru care nu cunoastem tipul in momentul scrierii clasei? La prima
vedere, problema pare asemanatoare cu cea descrisa pentru metoda ToString(), deci rezolvarea ar
fi implementarea unui metode virtuale pe care clasele derivate din clasa colectie initiala sa o
suprascrie. Solutia nu este tocmai una potrivita, pentru ca de obicei nu exista o relatie de mostenire
intre clasa colectie si obiectele retinute. Solutia este de a impune ca toate obiectele sa ofere o
metoda, cum ar fi CompareTo(), pe care colectia sa o apeleze, permitand astfel colectiei sa
compare obiectele unul cu celalt:
int CompareTo(object obj)
{
// return 0 if this instance is equal to obj
// return < 0 if this instance is less than obj
// return > 0 if this instance is greater than obj
}

Prin urmare, clasa colectie poate folosi aceasta metoda pentru a sorta obiectele pe care le contine.
Prin urmare se poate defini o interfata pentru a colecta obiecte care contin metoda CompareTo()
si apoi sa se specifice ca clasa colectie de mai sus poate colecta doar clase care implementeaza
aceasta interfata. In acest fel o interfata este similara unui contract daca o clasa implementeaza
o interfata, atunci interfata garanteaza ca toate metodele din ea vor fi incluse in clasa. Acest
mecanism asigura ca vom putea apela metoda CompareTo() pentru toate obiectele din colectie si le
vom putea sorta.
8
Fundamentals of C# 2010

Altfel spus, interfata permite o separare clare intre ce si cum. In interfata se declara numai
numele, tipul returnat si parametrii unei metode. Cum este mai exact implementata metoda, nu este
treaba interfetei. Interfata descrie functionalitatea pe care o clasa trebuie sa o implementeze, nu si
cum sa o implementeze.
Definirea unei interfete se face folosind cuvantul-cheie interface in loc de class sau struct. In
interfata se definesc metode exact ca intr-o clasa sau o structura, cu diferenta ca nu se specifica
modificator de acces (public, private, protected). De asemenea, metodei nu i se furnizeaza nicio
implementare, corpul ei fiind inlocuit de ;. De exemplu :
public interface IComparable
{
int CompareTo(object obj);
}

Implementarea unei interfete se face declarand o clasa (sau structura) care mosteneste (e
derivata din) interfata si implementeaza toate metodele declarate in interfata. Daca revenim la
exemplul de mai devreme, clasele Mamifer, Cal si Balena, presupunem ca dorim definirea unei
metode NumarPicioare() care sa intoarca un int (pentru numarul de picioare) pentru mamiferele
care traiesc pe uscat. Putem defini o interfata IMamiferTerestru care contine o metoda :
interface IMamiferTerestru
{
int NumarPicioare();
}

Putem implementa aceasta interfata in clasa Cal (evident, balena nefiind mamifer terestru si
neavand picioare):
class Cal : Mamifer, IMamiferTerestru
{

public int NumarPicioare()
{
return 4;
}
}

Cand se implementeaza o interfata, trebuie respectate cateva reguli:


- Numele metodei si tipul intors trebuie sa se potriveasca exact;
- Orice tip de parametru (inclusiv ref si out) trebuie sa se potriveasca perfect;
- Toate metodele din interfata implementate in clasa trebuie sa fie publice daca sunt
implementate implicit; daca sunt implementate explicit, atunci metodele nu trebuie sa fie
precedate de niciun specificator de acces;

O clasa poate sa extinda (sa fie derivata din) o singura alta clasa, dar oricate interfete. Regula
este inca ca prima dupa : fie clasa, apoi interfetele, separate prin ,.

9
Fundamentals of C# 2010

La fel cum am vazut ca se poate referi un obiect utilizand o variabila definita de tipul unei clase
aflate la un nivel superior in ierarhia claselor, tot asa se poate face referinta la un obiect utilizand o
variabila de tipul interfata pe care clasa o implementeaza:

Cal horse = new Cal("Cezar");


IMamiferTerestru mamiferTerestru = horse;

Aceasta atribuire este valabila deoarece calul este un mamifer terestru.

Aceasta tehnica a referirii unui obiect printr-o interfata este utila pentru ca permite definirea de
metoda care sa primeasca diverse tipuri de parametri cat timp tipul respectiv implementeaza o
interfata. De exmplu, putem defini o metoda VitezaPeUscat() care ia ca si parametru de intrare orice
obiect care implementeaza interfata IMamiferTerestru:
int VitezaPeUscat(IMamiferTerestru mamiferTerestru)
{
if (mamiferTerestru is Cal)
{
return 60;
}
return 0;
}

Reguli ale interfetelor :


- Nu se permite definirea niciunui camp intr-o interfata;
- Nu se permite definirea unui constructor sau destructor intr-o interfata;
- Nu se permite niciun specificator de acces pentru metodele interfetei; toate metodele dintr-o
interfata sunt implicit publice;
- Nu se poate insera niciun alt tip de data (enumerari, structuri, clase sau alte interfete) intr-o
interfata;
- Nu este permis ca o interfata sa fie derivata dintr-o clasa sau structura, dar poate mosteni o alta
interfata;

2.2. Clase abstracte

Exista situatii cand se doreste ca o clasa definita sa nu poate fi instantiata. Asta pentru ca ea a
fost creata doar pentru a oferi o implementare comuna implicita a unor metode definite intr-o
eventuala interfata pe care clasa o implementeaza. In acest caz, clasa in cauza este numita
abstracta si se declara folosind cuvantul-cheie abstract prefixat declaratiei clasei.
O clasa abstracta este poate contine metode abstracte. O metoda abstracta este in principiu
similara uneia virtuale, cu execptia faptului ca aceasta nu contine nicio instructiune (nu are corp). O
metoda abstracta este utila atunci cand nu are sens sa i se furnizeze vreo implementare implicita in
clasa abstracta, ci se doreste ca ea sa fie implementata in clasa derivata (clasa derivata sa ofere
propria implementare).

10
Fundamentals of C# 2010

3. Proprietati

Asa cum am prezentat in capitolele anterioare, unul dintre avantajele si principiile POO este
incapsularea. Ideea incapsularii este ca avand definita o clasa, sa stabilim noi ce anume se va
vedea din clasa de afara (ce este public) si ce anume nu trebuie sa se vada (ce este privat). Am
vazut atunci ca o metoda comuna era declararea campurilor din clasa ca si private si initializarea lor
cu valori date la instantierea clasei, in constructorul clasei. Aceasta metoda este buna pana la un
punct daca se doreste ca aceste campuri private sa poate fi initializate din afara o singura data, la
construirea obiectului, si apoi sa nu mai existe acces la ele. In practica de multe ori este nevoie insa
ca anumite campuri dintr-o clasa sa poate fi modificate din exteriorul clasei si dupa ce obiectul a fost
creat (deci nu mai putem utiliza constructorul). Mai mult, e nevoie uneori ca anumite campuri ale
clasei sa poata fi doar citite (dupa ce in prealabil au fost initializate o data). Aceasta problema se
poate rezolva cu ajutorul proprietatilor.
Proprietatile sunt un fel de mixt intre un camp dintr-o clasa si o metoda din clasa. Nu e nici
camp, nici metoda. Seamana cu o metoda, dar se comporta ca si un camp.
Sintaxa pentru declararea unei proprietati este :
<specificator_de_acces> <tip> <nume_proprietate>
{
get
{
//cod pentru a accesa
}
set
{
//cod pentru a scrie
}
}

O proprietate poate contine doua blocuri de cod:


- blocul get contine instructiuni ce se executa cand proprietatea este citita;
- blocul set contine instructiuni ce se executa cand proprietatea este scrisa;
Tipul definit pentru proprietate semnifica tipul de date citit din proprietate sau scris in proprietate.
De exemplul definim o structura pentru a retine pozitia de pe ecran, in coordonate X, Y:
struct ScreenPosition
{
private int x, y;

public ScreenPosition(int X, int Y)


{
this.x = rangeCheckedX(X);
this.y = rangeCheckedY(Y);
}

public int X
{
11
Fundamentals of C# 2010

get { return this.x; }


set { this.x = rangeCheckedX(value); }
}

public int Y
{
get { return this.y; }
set { this.y = rangeCheckedY(value); }
}

private static int rangeCheckedX(int x) { ... }


private static int rangeCheckedY(int y) { ... }
}

Utilizarea proprietatilor in expresii se poate face fie in citire (cand se citeste valoarea
proprietatii), fie in scriere (cand i se da proprietatii o valoare). De exemplu :
ScreenPosition origin = new ScreenPosition(0, 0);
int xpos = origin.X; // calls origin.X.get
int ypos = origin.Y; // calls origin.Y.get

origin.X = 10;
origin.Y = 10;

Daca pentru o proprietate s-a definit doar blocul get, atunci am obtinut o proprietate read-only.
Prin urmare proprietatea respectiva poate fi poar citita, in ea nu se poate scrie nimic din exterior.
Daca pentru o proprietate s-a definit doar blocul set, atunci am obtinut o proprietate write-only.
Prin urmare proprietatea respectiva poate fi poar scrisa, din ea nu se poate citi nimic din exterior.

Cateva reguli se impun si pentru proprietati:


- o proprietate poate fi scisa doar dupa initializarea structurii sau clasei care contine proprietatea;
- o proprietate nu poate fi utilizata ca un argument out sau ref transmis unei metode;
- o proprietate poate contine cel mult un get si cel mult un set; nu poate contien alte metode,
campuri sau proprietati;
- get si set nu pot avea parametri;
- nu se pot declara proprietati constante

Proprietatile pot fi declarate in cadrul interfetelor. In acest caz se definesc get si set, doar ca nu
contin nimic:
interface IScreenPosition
{
int X { get; set; }
int Y { get; set; }
}

Orice clasa sau structura care implementeaza interfata trebuie sa implementeze si proprietatile
definite in interfata.

12
Fundamentals of C# 2010

4. Tipul generic

Pentru a intelege tipul generic (generics), revenim mai in detalii la o problema pe care o
introduce folosirea tipului obiect (obiect).
Tipul obiect se poate folosi oricand pentru a ne referi la o valoarea sau o variabila de orice tip.
Toate tipurile referinta sunt derivate (sau mostenesc) direct sau indirect clasa System.Object.
Datorita acestui lucru, se pot crea clase si metode super-generale, care sa lucreze doar cu obiecte.
De exemplu, multe dintre clasele namespace-ului System.Collections utilizeaza aceasta
oportunitate, deci putem crea colectii care sa contina aproape orice tip de data. Daca revenim la
unul dintre tipurile de colectii descrise in capitolul 4 din cursul trecut, si anume la clasa
System.Collections.Queue, am observat ca putem crea cozi (liste queue) care sa contina practic
orice. Reluam exemplul utilizarii clasei :

Queue coadaCercuri = new Queue();


Cerc c = new Cerc();
coadaCercuri.Enqueue(c);

c = (Cerc)coadaCercuri.Dequeue();

Metoda Enqueue() adauga un obiect in coada, iar metoda Dequeue() scoate un element din coada.
Deoarece aceste doua metode manipuleaza obiecte, se poate crea o coada de obiecte Cerc, Cal,
Mamifer etc, deci de orice tip doresc. Pe de alta parte este important a se observa ca a trebuit sa se
faca o conversie explicita (cast) a valorii returnate de catre metoda Dequeue() (adica un obiect) la
tipul de data potrivit (in acest caz la tipul Cerc), deoarece compilatorul nu face implicit conversia
unui obiect la un tip anume. Daca nu se face conversia explicita, atunci se obtine o eroare de
compilare. Drept urmare, aceasta necesitate de a face o conversie explicita stirbeste mult din
flexibilitatea pe care o ofera tipul obiect. Mai ales ca este foarte simplu sa gresim scriind :

Queue coadaCercuri = new Queue();


Cerc c = new Cerc();
coadaCercuri.Enqueue(c);

Mamifer m = (Mamifer)coadaCercuri.Dequeue();

Desi codul de mai sus va compila, el nu este valid, generandu-se o exceptie la rulare. Cauza este
evident, incerc sa pun intr-un obiect de tip Mamifer un obiect de tip Cerc. Aceasta eroare nu poate fi
descoperita decat la run-time.
Un alt dezavantaj al utilizarii tipului obiect pentru a crea clase si metode cat mai generale este
surplusul de memorie si de timp procesor necesare atunci cand la run-time este necesara o
conversie intre tipul obiect si alt tip, sau invers.
Solutia la aceste probleme cauzate de folosirea tipului obiect peste tot, este asa-numitul tip
generic. Aceasta solutia aduce o serie de avantaje precum inlaturarea nevoii de conversie explicita,
creste stabilitatea aplicatiei avant tipuri bine definite, reduce operatiile de boxing/unboxing, si
pastreaza capacitatea de a crea clase si metode generale. Spre deosebire de ce am vazut pana
acum, clasele si metodele generice accepta ca si parametri direct tipul de data, care are
13
Fundamentals of C# 2010

semnificatia de tipul de date cu care clasele si metodele vor opera. Librariile din framework-ul .NET
ofera versiuni generice ale multora dintre clasele de tip colectie si interfetelor, in namespace-ul
System.Collections.Generic. De exemplul :
Queue<Cerc> coadaCercuri = new Queue<Cerc>();
Cerc c = new Cerc();
coadaCercuri.Enqueue(c);
c = coadaCercuri.Dequeue();

Tipul definit intre semnele <> reprezinta tipul de date pe care coadaCercuri le accepta. Orice
metoda s-ar apela din aceasta clasa Queue, ea (metoda) se astepata sa primeasca un obiect de tip
Cerc in primul rand. Mai mult decat atat,verificarile de proptrivire de tipuri se fac chiar la compilare,
generandu-se erori daca este cazul. Atfel, erorile la run-time, cum am vazut in primul exemplu, se
elimina din start.
Este de asemenea posibil ca o clasa generica sa aiba mai multi parametri tip. De exemplu,
clasa generica System.Collections.Generic.Dictionary astepata doi parametri : unul pentru tipul cheii
din dictionar, celalalt pentru tipul valorii puse in dictionar, corespunzator cheii.:
public class Dictionary<TKey, TValue>

5. Supraincarcarea operatorilor

Daca ne amintim ce am spus in capitolele de inceput, operatorii sunt simboluri care actioneaza
asupra unor date, numite operanzi. Cei mai cunoscuti operatori sunt, evident, cei matematici. Pana
acum, operatorii actionau doar asupra operanzilor numerici. Ar fi interesant deci daca am putea face
ca un operator aritmetic (matematic) sa actioneze si asupra altor tipuri de date, inclusiv date de
tipuri referinta.
Asa cum am vazut pana acum, limbajul C# permite sa supraincarcam metode care se defineste
propriul tip de date (propria clasa). Similar, C# sa supraincarcam multi dintre operatorii obisnuiti.
Totusi, exista niste reguli pentru a realiza asta :
- Nu se poate modifica prioritatea si asociativitatea operatorilor;
- Nu se poaet schimba numarul de operanzi la care se aplica un operator; de exemplu, operatorul
* (multiplicare) este un operator binar, adica are nevoie de doi operanzi;
- Nu se pot inventa noi operatori;
- Exista si operatori care nu se pot supraincarca. De exemplu, nu se poate supraincarca
operatorulm . (punct) care indica accesul la un membru al unei clase.

Pentru a intelege mai bine de ce se doreste supraincarcarea operatorilor, dam un exemplu :


avem o clasa numita Complex care retine numere complexe din matematica, de forma z = a + b*i.
Putem foarte bine modela un numar complex cu aceasta clasa :

class Complex
{
14
Fundamentals of C# 2010

private float a;
private float b;

public Complex()
{
a = 0;
b = 0;
}

public Complex(float parteReala, float parteImaginara)


{
a = parteReala;
b = parteImaginara;
}
}

Putem apoi defini foarte bine doua numere complexe conform acestei clase:
Complex z1 = new Complex(1, 2);
Complex z2 = new Complex(3, 4);

Ca si in matematica, adunarea a doua numere complexe este perfect valabila. Dar, daca declar :
Complex z3 = z1 + z2;
voi obtine o eroare de compilare, caci operatorul + nu stie sa actioneze asupra obiecteleor de tip
Complex. Prin urmare, ceea ce incercam prin supraincarcarea operatorilor este sa-i invatam sa
efectueze operatii si asupra altor tipuri de date decat cele pe care ei implicit le cunosc.
Sintaxa pentru supraincarcarea operatorilor este asemanatoare cu cea a declararii unei metode,
cu deosebirea ca, in cazul nostru, numele metodei trebuie sa fie cuvantul-rezervat operator si urmat
imediat de simbolul, operatorul, supraincarcat. Pentru exemplul de mai sus, putem supraincarca
operatorul + astfel :
class Complex
{
public float Re
{
get { return this.a; }
}

public float Im
{
get { return this.b; }
}
.

public static Complex operator +(Complex z1, Complex z2)


{
return new Complex(z1.Re + z2.Re, z1.Im + z2.Im);
}
.
}

Pentru supraincarcarea operatorilor trebuie ca :


15
Fundamentals of C# 2010

- Operatorul sa fie declarat publi si static;


- Un operator binar (de exemplu +) trebuie sa aiba doi parametri (operanzi), iar unul unar (de
exemplu ++) trebuie sa aiba un singur parametru;

Bineinteles, asa cum spune si numele, supraincarcarea unui operator poate merge mai departe si
poate primi diverse implementari. De exemplu, o implementare ar fi sa permitem adunarea la un
numar complez a unei valoari numerice pur si simplu :
Complex z3 = z1 + 10;
rezultatul fiind, evident, tot un numar complex:
public static Complex operator +(Complex z1, Complex z2)
{
return new Complex(z1.Re + z2.Re, z1.Im + z2.Im);
}

public static Complex operator +(Complex z1, float f)


{
return new Complex(z1.Re + f, z1.Im);
}

Exact in acelasi mod se pot supraincarcarea si operatorii unari (++, --), cu conditia ca fiecare sa
primeasca un singur parametru, precum si operatorii de egalitate (==), respectiv inegalitate (!=).

16

Anda mungkin juga menyukai