В отличие от ссылочных типов переменным/параметрам значимых типов нельзя напрямую присвоить значение null. Тем не менее нередко бывает удобно, чтобы переменная/параметр значимого типа могли принимать значение null, Например, получаем числовое значение из базы данных, которое в бд может отсутствовать. То есть, если значение в базе данных есть - получим число, если нет - то null.
Чтобы присвоения переменной или параметру значимого типа значения null, эти переменная/параметр значимого типа должны представлять тип nullable. Для этого после названия типа указывается знак вопроса ?
int? val = null;
Console.WriteLine(val);
Здесь переменная val представляет не просто тип int, а тип int? - тип, переменные/параметры
которого могут принимать как значения типа int, так и значение null. В данном случае мы передаем ей значение
null. Но также можно передать и значение типа 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);
}
Однако если переменная/параметр представляет значимый не nullable-тип, то присвоить им значение null не получится:
int val = null; // ! ошибка, переменная val НЕ представляет тип nullable Стоит отметить, что фактически запись ? для значимых типов является упрощенной формой использования структуры System.Nullable<T>. Параметр
T в угловых скобках представляет универсальный параметр, вместо которого в программе подставляется конкретный тип
данных. Следующие виды определения переменных будут эквивалентны:
int? number1 = 5;
Nullable<int> number2 = 5;
Свойства Value и HasValue и метод GetValueOrDefault
Структура Nullable<T> имеет два свойства:
Value - значение объекта
HasValue: возвращает
true, если объект хранит некоторое значение, иfalse, если объект равенnull.
Мы можем использовать эти свойства для проверки наличия и получения значения:
PrintNullable(5); // 5
PrintNullable(null); // параметр равен null
void PrintNullable(int? number)
{
if (number.HasValue)
{
Console.WriteLine(number.Value);
// аналогично
Console.WriteLine(number);
}
else
{
Console.WriteLine("параметр равен null");
}
}
Однако если мы попробуем получить через свойство Value значение переменной, которая равна null, то мы столкнемся с ошибкой:
int? number = null;
Console.WriteLine(number.Value); // ! Ошибка
Console.WriteLine(number); // Ошибки нет - просто ничего не выведет
Также структура Nullable<T> имеет метод GetValueOrDefault(). Он возвращает значение переменной/параметра, если они не равны null. Если они равны null, то возвращается значение по умолчанию. Значение по умолчанию можно передать в метод. Если в метод не передается данных, то возвращается значение по умолчанию для данного типа данных (например, для числовых данных это число 0).
int? number = null; // если значения нет, метод возвращает значение по умолчанию
Console.WriteLine(number.GetValueOrDefault()); // 0 - значение по умолчанию для числовых типов
Console.WriteLine(number.GetValueOrDefault(10)); // 10
number = 15; // если значение задано, оно возвращается методом
Console.WriteLine(number.GetValueOrDefault()); // 15
Console.WriteLine(number.GetValueOrDefault(10)); // 15
Преобразование значимых nullable-типов
Рассмотрим возможные преобразования:
-
явное преобразование от T? к T
int? x1 = null; if(x1.HasValue) { int x2 = (int)x1; Console.WriteLine(x2); } -
неявное преобразование от T к T?
int x1 = 4; int? x2 = x1; Console.WriteLine(x2);
-
неявные расширяющие преобразования от V к T?
int x1 = 4; long? x2 = x1; Console.WriteLine(x2);
явные сужающие преобразования от V к T?
long x1 = 4; int? x2 = (int?)x1;
Подобным образом работают явные сужающие преобразования от V? к T?
long? x1 = 4; int? x2 = (int?)x1;
явные сужающие преобразования от V? к T
long? x1 = null; if (x1.HasValue) { int x2 = (int)x1; }
Операции с nullable-типами
nullable-типы поддерживают тот же набор операций, что и их не-nullable двойники. Но следует учитывать, что если в операции участвует nullable-тип, то результатом также будет значение nullable-типа
int? x = 5;
int z = x + 7; // нельзя
int? w = x + 7; // можно
int d = x.Value + 7; // можно
В арифметических операциях, если один из операндов равен null, то результатом операции также будет null:
int? x = null;
int? w = x + 7; // w = null
В операциях сравнения >, <, >= и <=, если хотя бы один из операндов равен null, то возвращается false (кроме операции !=):
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
В операциях же == и != идет стандартное сравнение:
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