Tipuri Nullable (?, ??, ?.) în C# (.NET)

Bazele limbajului și I/O C# 2.0+ (Nullable Reference Types în C# 8.0+)

Reprezintă explicit absența unei valori și previne excepțiile NullReferenceException prin operatori siguri de navigare și coalescență.

Sintaxă C#
int? varsta = null; | string nume = input ?? "Implicit"; | int? lung = text?.Length;

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 .Value pe un int? fără a verifica înainte HasValue (sau is not null), ceea ce aruncă InvalidOperationException: Nullable object must have a value. Preferă val ?? 0 sau val.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 lui NullReferenceException la rulare.

Funcții și concepte înrudite