Null și tipurile valorice
Spre deosebire de tipurile de referință, variabilele/parametrii de tipuri valorice nu pot avea valoarea null. Totuși, este adesea convenabil ca o variabilă/parametru de tip valoric să poată lua valoarea null.
De exemplu, când primim o valoare numerică dintr-o bază de date, aceasta poate lipsi. Adică, dacă valoarea este în baza de date, primim un număr; dacă nu, primim null.
Pentru a permite atribuirea valorii null unei variabile sau unui parametru de tip valoric, acestea trebuie să reprezinte un tip nullable. Pentru aceasta, se adaugă semnul de întrebare (?) după numele tipului:
int? val = null;
Console.WriteLine(val);
Aici, variabila val nu reprezintă doar tipul int, ci tipul int? - un tip ale cărui variabile/parametri pot avea atât valori de tip int, cât și valoarea null. În acest caz, îi atribuim valoarea null. Dar putem atribui și o valoare de tip int:
int? val = null;
IsNull(val); // null
val = 22;
IsNull(val); // 22
void IsNull(int? obj)
{
if (obj == null) Console.WriteLine("null");
else Console.WriteLine(obj);
}
Totuși, dacă variabila/parametrul reprezintă un tip valoric non-nullable, atribuirea valorii null nu va fi posibilă:
int val = null; // ! eroare, variabila val nu reprezintă un tip nullable
Este de menționat că, de fapt, sintaxa cu semnul ? pentru tipurile valorice este o formă simplificată a utilizării structurii System.Nullable<T>. Parametrul T din parantezele unghiulare reprezintă un parametru generic, în locul căruia se introduce un tip de date specific în program.
Următoarele tipuri de definiții ale variabilelor sunt echivalente:
int? number1 = 5;
Nullable<int> number2 = 5;
Proprietățile Value și HasValue și metoda GetValueOrDefault
Structura Nullable<T> are două proprietăți:
- Value - valoarea obiectului
- HasValue - returnează true dacă obiectul stochează o valoare, și false dacă obiectul este null
Putem folosi aceste proprietăți pentru a verifica prezența și obținerea valorii:
PrintNullable(5); // 5
PrintNullable(null); // parametrul este null
void PrintNullable(int? number)
{
if (number.HasValue)
{
Console.WriteLine(number.Value);
// similar
Console.WriteLine(number);
}
else
{
Console.WriteLine("parametrul este null");
}
}
Totuși, dacă încercăm să obținem valoarea unei variabile care este null prin proprietatea Value, vom întâmpina o eroare:
int? number = null;
Console.WriteLine(number.Value); // ! Eroare
Console.WriteLine(number); // Fără eroare - nu va afișa nimic
Structura Nullable<T> are și metoda GetValueOrDefault(). Aceasta returnează valoarea variabilei/parametrului dacă acestea nu sunt null. Dacă sunt null, returnează valoarea implicită.
Valoarea implicită poate fi transmisă metodei. Dacă nu se transmite nicio valoare, se returnează valoarea implicită pentru tipul de date respectiv (de exemplu, pentru date numerice este numărul 0).
int? number = null; // dacă nu există valoare, metoda returnează valoarea implicită
Console.WriteLine(number.GetValueOrDefault()); // 0 - valoarea implicită pentru tipurile numerice
Console.WriteLine(number.GetValueOrDefault(10)); // 10
number = 15; // dacă există valoare, aceasta este returnată de metodă
Console.WriteLine(number.GetValueOrDefault()); // 15
Console.WriteLine(number.GetValueOrDefault(10)); // 15
Convertirea tipurilor valorice nullable
Să vedem posibilele conversii:
- Conversie explicită de la T? la T
int? x1 = null;
if(x1.HasValue)
{
int x2 = (int)x1;
Console.WriteLine(x2);
}
- Conversie implicită de la T la T?
int x1 = 4;
int? x2 = x1;
Console.WriteLine(x2);
- Conversie implicită extinsă de la V la T?
int x1 = 4;
long? x2 = x1;
Console.WriteLine(x2);
- Conversie explicită restrânsă de la V la T?
long x1 = 4;
int? x2 = (int?)x1;
- În mod similar, funcționează conversiile explicite de restrângere de la V? la T?
long? x1 = 4;
int? x2 = (int?)x1;
- Conversiile explicite de restrângere de la V? la T
long? x1 = null;
if (x1.HasValue)
{
int x2 = (int)x1;
}
Operații cu tipurile nullable
Tipurile nullable suportă același set de operații ca și omologii lor non-nullable. Dar trebuie să ținem cont că, dacă într-o operație participă un tip nullable, rezultatul va fi tot un tip nullable.
int? x = 5;
int z = x + 7; // nu este permis
int? w = x + 7; // este permis
int d = x.Value + 7; // este permis
În operațiile aritmetice, dacă unul dintre operanzi este null, rezultatul operației va fi, de asemenea, null:
int? x = null;
int? w = x + 7; // w = null
În operațiile de comparație >, <, >= și <=, dacă cel puțin unul dintre operanzi este null, se returnează false (cu excepția operației !=):
int? x = null;
int? y = 5;
int? z = null;
Console.WriteLine($"x > y is {x > y}"); // false
Console.WriteLine($"x < y is {x < y}"); // false
Console.WriteLine($"x >= y is {x >= y}"); // false
Console.WriteLine($"x <= y is {x <= y}"); // false
Console.WriteLine($"x > z is {x > z}"); // false
Console.WriteLine($"x < z is {x < z}"); // false
Console.WriteLine($"x >= z is {x >= z}"); // false
Console.WriteLine($"x <= z is {x <= z}"); // false
În operațiile == și != se realizează comparația standard:
int? x = null;
int? y = 5;
int? z = null;
Console.WriteLine($"x == y is {x == y}"); // false
Console.WriteLine($"x != y is {x != y}"); // true
Console.WriteLine($"x == z is {x == z}"); // true
Console.WriteLine($"x != z is {x != z}"); // false