Thursday, March 28, 2024 13:31

Cuprins >> Obiecte > Membrii statici

Membrii statici

După cum am văzut în ultimele lecții anterioare, modul obișnuit de a comunica cu o clasă este de a crea instanțe (copii) ale acesteia și apoi de a folosi obiectele rezultate. De fapt, acesta este avantajul principal al claselor – capacitatea de a crea copii care pot fi folosite și pot fi modificate individual. Cu toate acestea, există cazuri în care este posibil să doriți să utilizați o clasă fără a o instanția, sau să accesați membrii acesteia fără a crea un obiect pentru ei. Acest tip special de membri, care sunt asociați cu tipul de date (clasa) și nu cu instanțele specifice, se numesc membri statici (static – deoarece rămân aceiași).

Deci, ca de obicei, am oferit definiția „academică” a ceea ce înseamnă static. Acum, să explic în cuvinte obișnuite. Ce se întâmplă dacă, să spunem, avem o clasă numită Carte, o vindem, și am vrea să știm câte copii am vândut? Primul nostru gând ar fi să creăm ceva de genul:

Deci, avem clasa noastră Carte, în care declarăm un câmp privat numit copii, care va stoca în mod intern valoarea copiilor noastre, și o proprietate publică numită Copii, pe care o folosim pentru a stoca numărul de copii pe care le vindem. În cele din urmă, în constructorul clasei noastre, incrementăm proprietatea Copii cu 1. Asta pentru a incrementa proprietatea noastră Copii de fiecare dată când instanțiem clasa noastră, adică de fiecare dată când creăm o copie a cărții, pentru a o vinde. Acum, puteți vedea cum funcționează:

De vreme ce am creat trei exemplare ale cărții noastre, ne-am fi așteptat ca proprietatea Copii să aibă valoarea 3. Din păcate, acesta ar fi rezultatul real:

static gresit

Deci, dacă am incrementat valoarea proprietății noastre de fiecare dată când am instanțiat clasa, de ce este afișată întotdeauna valoarea 1? Nu ar trebui să fie afișat 1, 2, 3, etc?

De fapt nu. Din lecția Instanțierea am aflat că atunci când instanțiem un obiect, realizăm de fapt copii individuale ale clasei noastre, care sunt separate una de cealaltă. Aceasta înseamnă că proprietatea Copii în instanța noastră primaCarte nu este aceeași proprietate Copii din aDouaCarte sau aTreiaCarte. De fiecare dată când am instanțiat clasa noastră, am creat de fapt o proprietate nouă, individuală și separată, care nu are nicio idee despre existența copiilor anterioare și a valorilor lor. Nu contează că o incrementăm în constructorul clasei – deci, de fiecare dată când creăm o copie – pentru că de fapt incrementăm o nouă proprietate Copii, de fiecare dată inițializată cu valoarea 0!

Deci ce putem face? Cum putem crea o proprietate care are valoarea sa independentă de orice instanțiere și păstrează valorile anterioare, chiar și atunci când vom crea o nouă copie a obiectului nostru?

Aici sunt folositori membrii statici. Astfel, încercăm această modalitate:

Și apoi afișăm rezultatele:

Și, voilà! Iată rezultatul:

iesire membrii statici

Ne-am atins scopul, acum vedem corect numărul de copii vândute, dar de ce funcționează de această dată? Nu este creată o nouă proprietate Copii de fiecare dată când creem o instanță, ca și înainte? Nu ar trebui să aibă valoarea de inițializare 0, de fiecare dată?

Aceasta este frumusețea membrilor statici. De fiecare dată când declarăm un câmp, proprietate, metodă, etc., cu cuvântul cheie static, instruim compilatorul NU să creeze o copie a acelui element, ci să creeze unul unic, care este disponibil și rămâne același indiferent de instanțele pe care le creem. Deci, cu alte cuvinte, pentru că am declarat proprietatea noastră Copii ca fiind statică, compilatorul a creat o proprietate singulară, unică, care a rămas aceeași pentru toate cele trei instanțe. Fiecare dintre instanțele noastre nu a creat o copie a proprietății Copii atunci când le-am creat, ci mai degrabă toate au indicat către aceeași proprietate unică. Aceasta înseamnă că atunci când am incrementat proprietatea Copii în constructori, am creat o proprietatea unică, care, fiind unică, a păstrat valorile anterioare. Pentru a înțelege mai bine acest lucru, voi recurge din nou la exemplul meu cu schița. Am spus câteva lecții în urmă că o clasă poate fi văzută ca o schiță, în timp ce instanțele sunt doar copii create după acea schiță. În exemplul nostru Carte, nu vindem manuscrisul original – clasa Carte în sine, ci creăm copii ale acesteia, numite instanțe. Membrii statici sunt membri care nu aparțin instanțelor (copiilor), ci manuscrisului, schiței, clasei originale. De aceea, indiferent câte copii creăm, rămân aceeași, cu alte cuvinte, statici.

Probabil ați observat în cel de-al doilea exemplu că sintaxa și modul în care afișăm rezultatul au fost ușor diferite față de cele din primul exemplu. Acest lucru se datorează faptului că membrii statici, fiind independenți de orice instanță, necesită tratament special. Voi exemplifica acest lucru în continuare, în această lecție.

Câmpurile statice, metodele, funcțiile, proprietățile, sunt declarate statice, de obicei declarându-le modificatorul de acces, urmat de tipul acestora și de cuvântul cheie static, înaintea numelui lor:

Dacă nu este specificat niciun modificator de acces, declarația începe direct utilizând cuvântul cheie static.

Din exemplul de mai sus, observăm că am declarat câmpul copii ca fiind static, iar în proprietatea noastră Copii nu am utilizat cuvântul cheie this. Acest lucru se datorează faptului că un element static nu poate accesa membrii de nivel de clasa non-statici. Nu are nici un pointer „this„. În cuvinte simple, deoarece elementele noastre statice sunt unice, și din moment ce this se referă la instanța specifică pe care o folosim în acel moment, ele nu sunt compatibile. Nu puteți avea un element static (unic) care să acceseze un element instanțiat (copiat). Dacă am încerca să folosim un câmp non static în interiorul unei proprietăți statice, astfel:

am fi primit o eroare de compilator: O referință obiect este necesară pentru câmpul, metoda sau proprietatea non-statică „Carte.copii”. (An object reference is required for the non-static field, method, or property ‘Carte.copii’).

De asemenea, dacă am încerca să folosim cuvântul cheie this în proprietatea noastră, astfel:

compilatorul ar fi afișat două erori: Cuvântul „this” nu este valabil într-o proprietate statică, metodă statică sau inițializator de câmp static (Keyword ‘this’ is not valid in a static property, static method, or static field initializer), și „Carte.copii” nu poate fi accesat cu o referință de instanță; calificați-o în schimb cu un nume de tip (‘Carte.copii’ cannot be accessed with an instance reference; qualify it with a type name instead).

Acest lucru se datorează faptului că, așa cum am explicat, un element static (unic) nu poate comunica cu elemente instanțiate (copiate, non-unice). Pe de altă parte, elementele statice sunt perfect accesibile din elemente non-statice. Aceasta înseamnă că orice element instanțiat (copie) al unei clase are acces la, și poate modifica un element static (unic) din acea clasă. De aceea, putem să incrementăm proprietatea noastră statică Copii în constructorul nostru non-static Carte().

În afară de aceasta, în exemplul nostru, am accesat proprietatea noastră Copii astfel:

Puteți observa în mod clar că deși am instanțiat clasa noastră Carte ca un obiect numit primaCarte, nu am accesat proprietatea Copii prin instanța respectivă, astfel:

Dacă am făcut acest lucru, am fi obținut o eroare de compilator: „Carte.Copii” nu poate fi accesată cu o referință de instanță; Calificați-o în schimb cu un nume de tip (‘Carte.Copii’ cannot be accessed with an instance reference; qualify it with a type name instead). Puteți deja să intuiți că acest lucru se întâmplă deoarece de vreme ce elementele statice sunt unice, ele aparțin clasei originale, nu instanțelor. Acesta este motivul pentru care accesăm elementele statice utilizând direct clasa însăși. De asemenea, asta înseamnă că putem accesa un câmp static chiar fără a crea o instanță a acelei clase:

Codul de mai sus va afișa 0 pe consolă, deoarece am inițializat câmpul static copii cu valoarea 0, iar proprietatea statică Copii a returnat acea valoare. Deoarece nu am instanțiat clasa noastră, nu a existat niciun apel către constructorul acesteia, care să incrementeze valoarea proprietății. Cu toate acestea, putem să o incrementăm extern:

Și consola ar afișa acum 1.

Un lucru de observat aici: chiar dacă elementele non-statice pot accesa pe cele statice, trebuie să folosim în continuare instanțiere atunci când le accesăm:

Ar trebui să știți că elementele statice sunt puțin mai rapide și mai eficiente decât elementele non-statice. Acest lucru deoarece compilatorul le creează doar o singură dată în memorie, spre deosebire de elementele instanțiate, care sunt re-create de fiecare dată când instanțiem. Cu toate acestea, nu trebuie să vă bazați pe aceasta numai pentru a „face programul mai rapid”. Îmbunătățirea performanței este de cele mai multe ori neglijabilă.

Nu putem declara elemente statice în metode, funcții, proprietăți, constructori:

deoarece compilatorul s-ar plânge: Modificatorul „static” nu este valabil pentru acest element (The modifier ‘static’ is not valid for this item). Deoarece paginaCurenta este o variabilă locală, care trăiește numai în cadrul metodei IntoarcePagina(), care este o metodă statică, compilatorul știe că singura opțiune disponibilă pentru paginaCurenta este tot static. De aceea nu trebuie să precizăm că este statică în declarația sa.

Un alt lucru pe care ar trebui să-l știți: clasele pot fi și ele statice. În acest caz, nu pot fi instanțiate și nu pot avea un constructor non-static. Deci, aceasta:

Ar da o eroare de compilator: Clasele statice nu pot avea constructori instanțe (Static classes cannot have instance constructors). Deci, trebuie să modificăm constructorul nostru și să îl facem deasemeni static:

Dar aceasta ar genera o altă eroare: „Carte.Carte()”: modificatorii de acces nu sunt permiși pe constructorii statici (‘Carte.Carte()’: access modifiers are not allowed on static constructors). Acest lucru ne indică faptul că modul corect de a crea o clasă statică cu un constructor este acesta:

Constructorii statici sunt mai lenți decât majoritatea celorlalți constructori. De asemenea, ei provoacă probleme, deoarece sunt instanțate „leneș”. Fiecare acces la clasă trebuie să verifice dacă constructorul static a fost executat. Deci, pe cât posibil, evitați constructorii statici.

Poate vă întrebați unde este apelat constructorul, de vreme ce nu instanțiem clasa noastră statică. Un constructor static este apelat automat înainte ca prima instanță să fie creată sau orice membri statici să fie referențiați. Este folosit pentru a inițializa orice date statice, sau pentru a efectua o anumită acțiune care trebuie efectuată o singură dată. Un constructor static este de asemenea numit inițializator de tip.

Odată ce declarăm o clasă statică, nu putem să o mai instanțiem:

deoarece ne va da doar erori: Nu se poate declara o variabilă de tip static „Carte” (Cannot declare a variable of static type ‘Carte’) și Nu se poate crea o instanță a clasei statice „Carte” (Cannot create an instance of the static class ‘Carte’).

Întrucât membrii statici nu pot accesa elemente non-statice, dacă declarăm o clasă statică, toate proprietățile, câmpurile, metodele și funcțiile acelei clase trebuie să fie de asemenea, statice. Nu putem avea aceasta:

din cauza următoarei erori: „Carte.paginaCurenta”: nu pot declara membri instanță într-o clasă statică (‘Carte.paginaCurenta’: cannot declare instance members in a static class).

O utilizare obișnuită a claselor statice, deși unele persoane se încruntă când aud de așa ceva, este ceea ce programatorii numesc clase utilitare/ajutătoare (utility/helper), unde colectați o grămadă de metode utile, care ar putea să nu aibă nimic în comun, dar nici nu se potrivesc în altă parte. De exemplu, puteți avea o clasă numită UtilitarFormatare unde aveți diferite metode pe care le utilizați permanent în programele voastre și care nu trebuie să fie instanțiate: FormateazaNumarTelefon(), FormateazaCodTara(), FormateazaSeparatorDecimal(), FormateazaData(), etc.

În cele din urmă, pentru a încheia această lecție, între clasele statice care nu pot fi instanțiate și clasele obișnuite care pot fi doar instanțiate, există un concept special de programare numit singleton, care este de fapt o clasă care poate fi instanțiată, dar numai o singură dată:

Codul de mai sus poate părea un pic complex, deoarece folosim câteva elemente despre care nu am învățat încă (sealed, readonly), dar dacă le ignorați, logica devine clară. Din motive de argumentare, să spunem doar că este esențial să le folosiți: readonly asigură thread safety, în timp ce sealed permite compilatorului să efectueze optimizări speciale în timpul compilării JIT. Constructorii privați înseamnă că clasa se poate aloca doar pe sine însăși.

Deci, pentru a vă ajuta să înțelegeți de ce clasa ClasaSingleton poate fi instanțiată o singură dată: avem o proprietate readonly numită Instanta care poate fi doar citită, nu scrisă. Observați că proprietatea este statică, ceea ce înseamnă că nu o putem accesa prin instanțe. De asemenea, observați că tipul proprietății este tipul clasei în sine. Aceasta înseamnă că proprietatea poate returna doar o valoare de tipul clasei (ClasaSingleton). Și acest lucru este adevărat, deoarece getter-ul returnează câmpul _instanta, care a fost declarat de tipul ClasaSingleton, ca o nouă instanță a clasei. Faptul că proprietatea este readonly, statică și de tip ClasaSingleton înseamnă că ori de câte ori avem acest cod:

de fapt creăm în mod intern o singură instanță a ClasaSingleton. Deoarece constructorul ClasaSingleton este privat, nu putem instanția clasa, iar din moment ce creem intern o singură instanță si o returnăm printr-o proprietate readonly, obtinem o clasă care poate fi instanțiată o singură dată.

Tags: , , , , ,

2 Responses to “Membrii statici”

  1. alx spune:

    felicitari!

Leave a Reply



Follow the white rabbit