book-openConstructori

Când creăm un obiect cu new fără să definim un constructor, C# îl inițializează cu valori implicite: null pentru tipuri referință, 0 pentru numere, false pentru bool.

Client c = new Client();
Console.WriteLine(c.Nume);    // null
Console.WriteLine(c.Email);   // null
Console.WriteLine(c.Telefon); // null

Obiectul există în memorie, dar se află într-o stare inutilizabilă. Dacă transmitem acest obiect la o metodă care presupune că are un email valid, sau dacă apelăm c.GetNumeComplet() care concatenează Prenume și Nume, obținem fie un rezultat incorect, fie o excepție NullReferenceException. În cel mai nefericit caz, eroarea va apărea în cu totul altă parte a programului decât locul unde s-a produs problema.

Sursa deficienței este că am separat crearea obiectului de inițializarea lui. Obiectul a putut fi creat fără ca datele necesare să fie furnizate. Constructorul rezolvă exact această problemă.

Ce este constructorul și cum funcționează?

Constructorul este o metodă specială apelată automat de operatorul new în momentul creării obiectului. Are același nume ca și clasa și nu are tip de retur, nici măcar void.

class Client
{
    public string Nume { get; set; }
    public string Prenume { get; set; }
    public string Email { get; set; }
    public string Telefon { get; set; }

    public Client(string nume, string prenume, string email, string telefon)
    {
        Nume = nume;
        Prenume = prenume;
        Email = email;
        Telefon = telefon;
    }
}

Odată definit acest constructor, new Client() fără parametri nu mai compilează. Singurul mod de a crea un client este să furnizezi toate datele de la început:

Efectul este că obiectul nu poate exista fără date. Nu este o restricție arbitrară, este o garanție: oricine primește un obiect Client știe că are toate câmpurile completate. Nu trebuie să verifice dacă Nume este null înainte de a-l folosi. Nu trebuie să se întrebe dacă obiectul a fost corect inițializat.

Constructorul și validarea din proprietăți

O decizie importantă de implementare este că, în constructor, valorile sunt atribuite prin proprietăți, nu direct în câmpuri. Aceasta înseamnă că validarea din blocurile set rulează automat în timpul construcției, fără niciun cod suplimentar.

Dacă apelăm:

...constructorul încearcă să atribuie "abc" proprietății Telefon. Proprietatea aruncă ArgumentException. Constructorul nu finalizează. Obiectul nu ajunge în memorie.

Consecința practică este că nu poți obține niciodată un obiect Client cu un număr de telefon invalid. Validarea nu trebuie apelată manual de fiecare dată. Ea este garantată structural, prin modul în care clasa este construită.

Acesta este motivul pentru care validarea se pune în proprietăți și se atribuie prin proprietăți în constructor, nu după construcție: un obiect invalid nu ar trebui să poată exista.

Constructori multipli

O clasă poate defini mai mulți constructori, cu liste de parametri diferite. Mecanismul se numește supraîncărcare și permite crearea obiectelor în moduri diferite, în funcție de ce date sunt disponibile la momentul creării.

Compilatorul alege constructorul potrivit în funcție de numărul și tipul argumentelor transmise la new. Dacă niciun constructor nu se potrivește, codul nu compilează — eroarea este detectată devreme.

Delegarea între constructori cu : this(...)

Când mai mulți constructori conțin cod comun, duplicarea poate fi evitată prin delegare: un constructor îl apelează pe altul cu : this(...).

Constructorul delegat rulează primul, urmat de corpul constructorului care a făcut delegarea. Aceasta asigură că validările și inițializările comune au loc o singură dată, indiferent de ce constructor este apelat.

Constructorul în clasele derivate - : base(...)

Când o clasă moștenește altă clasă, constructorul clasei derivate trebuie să asigure inițializarea și a părții moștenite din obiect. Aceasta se realizează cu : base(...), care apelează explicit constructorul clasei de bază.

Ordinea de execuție este întotdeauna de sus în jos în ierarhie: mai întâi rulează constructorul clasei de bază (Bilet), abia apoi continuă constructorul clasei derivate (BiletStudent). Nu poți accesa sau inițializa membrii clasei de bază din constructorul derivat înainte ca constructorul de bază să fi rulat. Compilatorul asigură această ordine.

Dacă uiți : base(...) și clasa de bază nu are constructor fără parametri, codul nu compilează. Compilatorul nu are cum să știe cum să inițializeze partea moștenită.

Importanța practică este că validările din clasa de bază rulează și în constructorii claselor derivate. Dacă Bilet validează că prețul de bază este pozitiv, atunci un BiletStudent cu preț negativ nu poate fi creat. Validarea este moștenită implicit prin lanțul de constructori.

Constructorul implicit

Dacă nu definești niciun constructor, compilatorul C# generează automat un constructor implicit fără parametri care nu face nimic. Acesta este motivul pentru care new Client() funcționează pe o clasă fără constructor definit explicit.

De îndată ce definești cel puțin un constructor cu parametri, constructorul implicit generat automat dispare. Dacă mai ai nevoie și de un constructor fără parametri, trebuie să îl declari explicit:

Acesta este un comportament care surprinde frecvent: adaugi un constructor cu parametri la o clasă existentă și brusc tot codul care folosea new Client() fără parametri nu mai compilează. Motivul este tocmai că constructorul implicit a dispărut odată cu adăugarea celui explicit.

Last updated