Ce face Tipuri Nullable (?, ??, ?.)
În C# modern (.NET 6+, unde opțiunea <Nullable>enable</Nullable> este activată implicit în proiecte), atât tipurile valoare (int, bool, DateTime), cât și tipurile referință (string, User) sunt considerate non-nullable în mod implicit. Adăugarea sufixului ? la tip (de exemplu int?, care este o prescurtare pentru structura Nullable<int>, sau string?) îi transmite compilatorului că variabila poate conține în mod legitim valoarea null.
Pentru a lucra concis și sigur cu valori care pot fi null, C# pune la dispoziție trei operatori esențiali: operatorul de acces condițional ?. (care returnează null în loc să arunce NullReferenceException dacă obiectul din stânga este null), operatorul de coalescență ?? (care oferă o valoare de rezervă când expresia din stânga este null) și operatorul de atribuire la coalescență ??= (care inițializează variabila doar dacă aceasta este în prezent null).
Parametri, metode și opțiuni
| Parametru / Metodă | Descriere |
|---|---|
T? |
Declară că un tip valoare (int?) sau referință (string?) poate primi valoarea null. |
a ?? b |
Operatorul de coalescență: returnează valoarea lui a dacă nu este null; altfel evaluează și returnează b. |
a ??= b |
Atribuie valoarea b variabilei a doar dacă a este în acel moment null (C# 8.0+). |
obj?.Proprietate / arr?[i] |
Navigare sigură (null-conditional): evaluează membrul sau indexul doar dacă obj nu este null. |
val.HasValue / val.Value |
Proprietăți specifice pentru Nullable<T> (tipuri valoare): verifică prezența valorii și o extrage. |
expr! |
Operatorul null-forgiving: suprimă avertismentul compilatorului când știi sigur că expr nu este null (nu face verificări la rulare!). |
Exemple
Exemplul 1: Combinarea operatorilor ?. și ?? pentru extragerea sigură a datelor
Putem înlănțui operatorul ?. pe mai multe niveluri și încheia expresia cu ?? pentru a garanta o valoare implicită non-null fără blocuri if imbricate.
Client? client1 = new("Elena", new Adresa("Cluj-Napoca", "400001"));
Client? client2 = new("Mihai", Adresa: null);
Client? client3 = null;
foreach (var c in new[] { client1, client2, client3 })
{
string nume = c?.Nume ?? "Client neautentificat";
string oras = c?.Adresa?.Oras ?? "Oraș nespecificat";
int lungimeCod = c?.Adresa?.CodPostal?.Length ?? 0;
Console.WriteLine($"{nume} -> {oras} (lungime cod poștal: {lungimeCod})");
}
record Adresa(string Oras, string? CodPostal);
record Client(string Nume, Adresa? Adresa);
Ieșire în consolă
Elena -> Cluj-Napoca (lungime cod poștal: 6)
Mihai -> Oraș nespecificat (lungime cod poștal: 0)
Client neautentificat -> Oraș nespecificat (lungime cod poștal: 0)
Exemplul 2: Inițializare leneșă cu ??= și aruncarea excepțiilor direct în expresia ??
În C# 7+, partea dreaptă a operatorului ?? poate fi o expresie throw, ideală pentru validarea parametrilor sau a configurațiilor.
List<string>? etichete = null;
// Inițializează lista doar dacă este null:
etichete ??= new List<string>();
etichete.Add("dotnet8");
etichete ??= new List<string>(); // nu suprascrie lista existentă!
etichete.Add("csharp12");
Console.WriteLine($"Etichete ({etichete.Count}): {string.Join(", ", etichete)}");
int? reducereProcent = null;
int reducereAplicata = reducereProcent.GetValueOrDefault(5);
Console.WriteLine($"Reducere aplicată: {reducereAplicata}%");
Ieșire în consolă
Etichete (2): dotnet8, csharp12
Reducere aplicată: 5%
Greșeli frecvente
- Apelarea directă a proprietății
.Valuepe unint?fără a verifica înainteHasValue(sauis not null), ceea ce aruncăInvalidOperationException: Nullable object must have a value. Preferăval ?? 0sauval.GetValueOrDefault(). - Abuzul de operatorul null-forgiving (
obiect!.Metoda()) doar pentru a ascunde avertismentele compilatorului (CS8602) — operatorul!are efect exclusiv la compilare și nu previne apariția luiNullReferenceExceptionla rulare.
Funcții și concepte înrudite
var și tipurile de date (int, decimal, bool): Declară variabile puternic tipizate prin inferență la compilare (var) sau folosind tipurile fundamentale de valoare din .NET.switch expression și Pattern Matching: Evaluează expresii concise bazate pe forma, tipul, proprietățile sau intervalele valorilor folosind pattern matching modern.FirstOrDefault / SingleOrDefault: Extrage un singur element dintr-o secvență (primul găsit sau unicul existent) ori returnează o valoare implicită când nu există potriviri.