В отличие от ссылочных типов переменным/параметрам значимых типов нельзя напрямую присвоить значение 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;
Структура 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
Рассмотрим возможные преобразования:
явное преобразование от 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-типа
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