Null și tipurile de referință
Pe lângă valorile standard de tip numere, șiruri, limbajul C# are o valoare specială - null, care indică absența unei valori, lipsa datelor. Până acum, valoarea null a fost valoarea implicită pentru tipurile de referință.
Până la versiunea C# 8.0, toate tipurile de referință puteau avea valoarea null:
string name = null;
Console.WriteLine(name);
Dar, începând cu versiunea C# 8.0, în limbaj a fost introdusă conceptul de tipuri de referință nullable (nullable reference types) și contextul conștient de nullable - nullable aware context, în care pot fi folosite tipurile de referință nullable.
Pentru a defini o variabilă/parametru de tip referință ca fiind nullable, se adaugă semnul de întrebare (?) după numele tipului:
string? name = null;
Console.WriteLine(name); // nu va afișa nimic
De exemplu, metoda integrată Console.ReadLine(), care citește un șir de la consolă, returnează exact valoarea string?, nu doar string:
string? name = Console.ReadLine();
De ce este necesară valoarea null? În diverse situații, este convenabil ca obiectele să poată avea valoarea null, adică să fie nedefinite. Un exemplu standard este lucrul cu o bază de date, care poate conține valori null. Și este posibil să nu știm în prealabil ce vom obține din baza de date - o valoare definită sau null.
Aceste tipuri de referință nullable sunt disponibile doar în contextul nullable. Caracteristicile contextului nullable includ:
- Variabila de tip referință trebuie inițializată cu o valoare concretă, nu trebuie să i se atribuie valoarea null
- Variabilei de tip referință nullable i se poate atribui valoarea null, dar înainte de utilizare trebuie verificată pentru null
Începând cu .NET 6 și C# 10, contextul nullable este aplicat implicit tuturor fișierelor de cod din proiecte. De exemplu, dacă scriem în Visual Studio 2022 pentru un proiect .NET 6 exemplul anterior, vom primi un avertisment:

Deși contextul nullable este o opțiune pe care o putem gestiona. Să deschidem fișierul proiectului. Pentru aceasta, fie facem dublu click pe proiect, fie facem click dreapta pe proiect și din meniul apărut selectăm Edit Project File.

După aceasta, Visual Studio va deschide fișierul proiectului, care va arăta aproximativ astfel:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
Aici, linia:
<Nullable>enable</Nullable>
mai precis, elementul <Nullable> cu valoarea enable indică faptul că acest context nullable se va aplica întregului proiect.
De ce este problematic null? Deoarece această valoare înseamnă absența datelor. Dar, să presupunem că avem situația în care primim un șir din exterior și încercăm să îi accesăm funcționalitatea. De exemplu, în exemplul de mai jos, metoda ToUpper() este apelată pentru șir, ceea ce convertește toate caracterele șirului în majuscule:
string name = null;
PrintUpper(name); // ! NullReferenceException
void PrintUpper(string text)
{
Console.WriteLine(text.ToUpper());
}
Aici, la executarea apelului PrintUpper(name), ne vom confrunta cu o excepție NullReferenceException, iar programul se va încheia neașteptat. Cineva poate spune că situația este artificială - știm clar că în funcție se transmite null. Totuși, în realitate, datele pot veni din exterior, de exemplu, dintr-o bază de date, de undeva din rețea etc.
Și este posibil să nu știm în mod explicit dacă există date sau nu. Utilizarea tipurilor de referință nullable permite rezolvarea parțială a acestei situații. Parțial, deoarece avertismentele nu ne împiedică să compilăm și să rulăm programul de mai sus.
Totuși, contextul nullable permite utilizarea posibilităților de analiză statică, prin care putem vedea fragmente de cod potențial periculoase, unde ne putem confrunta cu NullReferenceException.
În plus, există posibilitatea ca Microsoft să schimbe abordarea față de null și NullReferenceException, iar astfel de avertismente să devină erori în versiunile viitoare, așa că este mai bine să fim pregătiți pentru aceasta acum.
De exemplu, modificăm exemplul anterior astfel:
string? name = null;
PrintUpper(name);
void PrintUpper(string? text)
{
Console.WriteLine(text.ToUpper());
}
Aici, analiza statică sugerează că în metoda PrintUpper există o situație potențial periculoasă, deoarece parametrul text poate fi egal cu null.

Dezactivarea contextului nullable
Pentru a dezactiva contextul nullable în fișierul de configurare a proiectului, este suficient să schimbăm valoarea opțiunii Nullable, de exemplu, la "disable":
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
</PropertyGroup>
</Project>
Dezactivând contextul nullable, nu vom mai putea folosi în fișierele de cod din proiect tipurile de referință nullable și, prin urmare, nu vom putea utiliza analiza statică integrată a situațiilor potențial periculoase, unde ne putem confrunta cu NullReferenceException.
Contextul nullable la nivel de bloc de cod
Putem activa contextul nullable la nivelul unor blocuri de cod individuale folosind directiva #nullable enable. Să presupunem că global avem dezactivat contextul nullable:
<Nullable>disable</Nullable>
Definim în fișierul Program.cs următorul cod:
#nullable enable // activăm contextul nullable la nivel de fișier
string? name = null;
PrintUpper(name);
void PrintUpper(string? text)
{
Console.WriteLine(text.ToUpper());
}
Prima linie permite activarea contextului nullable la nivelul întregului fișier.

Operatorul ! (null-forgiving operator)
Operatorul ! (null-forgiving operator) permite indicarea faptului că o variabilă de tip referință nu este null:
string? name = null;
PrintUpper(name!);
void PrintUpper(string text)
{
if(text == null) Console.WriteLine("null");
else Console.WriteLine(text.ToUpper());
}
Aici, dacă nu am fi folosit operatorul ! și am fi scris PrintUpper(name), compilatorul ne-ar fi afișat un avertisment. Dar în metodă verificăm deja pentru null, așa că chiar dacă în metodă se transmite null, nu ne vom confrunta cu probleme.
Și pentru a elimina avertismentul inutil, se folosește acest operator. Acest operator nu are niciun efect în timpul execuției codului și este destinat doar pentru analiza statică a compilatorului. În timpul execuției, expresia name! va fi echivalentă cu valoarea name.
Excluderea codului din contextul nullable
Cu ajutorul directivei speciale #nullable disable, putem exclude un anumit bloc de cod din contextul nullable. De exemplu:
#nullable disable
string text = null; // aici contextul nullable nu se aplică
#nullable restore
string? name = null; // aici contextul nullable se aplică din nou
Orice cod între directivele #nullable disable și #nullable restore va fi exclus din contextul nullable și, prin urmare, nu va fi supus analizei statice.