Programarea modernă pune un mare accent pe un concept numit imuabilitate, care, în cuvinte simple, este incapacitatea unui obiect de a se schimba sau de a fi schimbat, odată ce a fost creat. Acest lucru are mai multe motive, dar cele mai importante susțin că:
- dacă o valoare nu ar trebui să se schimbe, ar trebui să fim expliciți în privința acestui lucru, și să nu îl lăsăm la interpretare, sau la întâmplare/greșeli
- cu cât se schimbă mai puține valori într-un program, cu atât există mai puține șanse de erori, și cu atât mai ușoară este identificarea cauzei lor principale
- lizibilitatea codului este îmbunătățită, deoarece intenția de a nu schimba o valoare devine evidentă
- compilatorul poate face unele optimizări de performanță, știind că valoarea nu se va schimba niciodată
Imuabilitatea poate fi obținută în multe moduri și are diferite grade de flexibilitate. Cea mai strictă modalitate este utilizarea constantelor, cărora trebuie să li se atribuie o valoare direct la declarare, ce nu se poate modifica ulterior. La spectrul opus, proprietățile cu setter doar inițializare ne permit să le setăm în timpul declarației, în constructorul tipului ce le înglobează, și inițializatoarele de obiecte, care sunt rulate imediat după ce constructorul își termină execuția.
Între ele, avem membrii doar-citire (readonly), care pot fi setați doar atunci când îi declarăm, sau în constructorul clasei lor. Exemplu:
|
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
|
using
System;
namespace
BunaLume
{
class
Persoana
{
public readonly string nume = "Ion Popescu"; // poate fi setat aici
public readonly int varsta;
public Persoana(int varsta)
{
this.varsta = varsta; // poate fi setat si aici
}
private void OMetoda()
{
nume = "Maria Popescu"; // aceasta va genera o eroare de compilator
}
}
class
Program
{
static void Main(string[] args)
{
Persoana persoana = new Persoana(20);
persoana.varsta = 10; // si aceasta va genera o eroare de compilator
Console.ReadLine();
}
}
}
|
Să analizăm exemplul de mai sus. Avem două câmpuri numite nume și varsta, care au fost declarate folosind cuvântul cheie readonly. Aceasta înseamnă, în primul rând, că le putem stabili valori în timpul declarației lor, așa cum se specifică la începutul acestei lecții:
|
1
|
public readonly string nume = "Ion Popescu";
|
Aceasta înseamnă, de asemenea, că le putem schimba valorile în constructorul clasei lor:
|
1
2
3
4
|
public Persoana(int varsta)
{
this.varsta = varsta;
}
|
De fapt, acesta este modul în care veți folosi câmpurile readonly, în marea majoritate a cazurilor. Acest tipar este folosit de obicei împreună cu un subiect avansat (despre care vom învăța mult, mult mai târziu), numit injectare de dependențe (dependency injection), pentru că, dacă vă gândiți bine, asta reprezintă varsta pentru clasa Persoana: o dependență fără pe care clasa nu poate exista (nu poate fi instanțiată). Acest lucru se datorează faptului că cerem varsta în constructorul clasei, ceea ce face imposibilă instanțierea acelei clase fără a furniza o valoare pentru varsta. Deci, dacă o instanță a clasei Persoana nu poate fi creată fără ea, aceasta înseamnă că varsta este o dependență a clasei Persoana, de unde termenul de injectare de dependențe ("injectăm" dependența varsta în câmpul readonly varsta).
Acesta este și locul în care rolul cuvântului cheie this devine clar, atunci când ne referim la câmpul din clasă varsta pentru a-i atribui valoarea parametrului varsta.
Deci, dacă membrii readonly ar trebui să aibă valorile lor setate în principal prin intermediul parametrilor din constructor, și dacă oricum nu pot fi setați în afara clasei lor, aceasta înseamnă și că ar trebui să îi marcăm ca private (ceea ce ar trebui să facem oricum, deoarece sunt variabile, câmpuri; datele ar trebui expuse în afara claselor doar prin intermediul proprietăților!):
|
1
2
|
private readonly string nume = "Ion Popescu";
private readonly int varsta;
|
În cele din urmă, puteți observa cum obțineți o eroare de compilator atât când încercați să schimbați valoarea unui câmp readonly după ce constructorul a terminat de creat instanța:
|
1
2
3
4
|
private void OMetoda()
{
nume = "Maria Popescu";
}
|
sau din afara clasei sale:
|
1
2
|
Persoana persoana = new Persoana(20);
persoana.varsta = 10;
|
Cuvântul cheie readonly poate fi aplicat numai variabilelor declarate în interiorul claselor (câmpuri). Nu îl puteți utiliza pentru variabile locale sau pentru alte tipuri de membri. De exemplu, nu putem marca o proprietate ca fiind readonly, dar putem obține același rezultat prin nespecificarea unui setter (proprietate readonly), doar a unui getter.
Ca și în cazul înregistrărilor, rețineți că readonly nu se referă la sub-membri:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
namespace
BunaLume
{
class
Student
{
public readonly decimal[] note = new decimal[] { 10, 7, 9 };
private void OMetoda()
{
note = null; // eroare de compilator, membrii readonly nu pot fi schimbati
note[2] = 8; // membrii campurilor readonly pot fi modificati
}
}
}
|
În exemplul de mai sus, doar array-ul note însuși este readonly. Valorile elementelor sale, nu.