Nullable value type (тип значения, допускающий null) – это тип, который позволяет представить не только все значения своего нижележащего типа, но и значение null.
Для чего нужны nullable value types? Например, переменная типа int может иметь значения в диапазоне от -2 147 483 648 до 2 147 483 647. Однако в некоторых случаях бывает необходимо указать, что значение переменной не определено или отсутствует (например, значение столбца в строке базы данных). Для подобных случаев и были созданы nullable value types. Эти типы представляют собой экземпляры структуры System.Nullable<T>.
Определить переменную типа int, допускающую значение null, можно следующим образом:
Nullable<int> nullableInt;
Однако чаще используют сокращённый вариант записи:
int? nullableInt;
Для представленных выше объявлений C# из кода будет сгенерирован одинаковый IL код.
Как значения нижележащего типа, так и значение null записываются в переменную через простое присвоение:
int? nullableIntLhs = 62;
int? nullableIntRhs = null;
Свойство HasValue позволяет выяснить, содержит ли nullable value type переменная значение нижележащего типа:
int? iNullable = 62;
int result;
if (iNullable.HasValue)
result = iNullable.Value;
else
result = -1;
// result == 62
Кроме вызова свойства HasValue, проверить на наличие значение можно через сравнение с null. Следующие проверки эквивалентны, для них генерируется одинаковый IL код:
int? nullableInt = null;
bool hasValue1 = nullableInt.HasValue;
bool hasValue2 = nullableInt != null;
Свойство Value возвращает значение нижележащего типа, если оно имеется (Nullable<T>.HasValue - true). В противном случае будет выброшено исключение типа InvalidOperationException:
int? iNullable = ....;
int result;
if (iNullable.HasValue)
result = iNullable.Value; // OK
else
result = iNullable.Value; // InvalidOperationException
Метод T GetValueOrDefault() аналогичен свойству Value с тем отличием, что он не генерирует исключение, а возвращает default значение типа T, если отсутствует значение нижележащего типа:
int? iNullable = ....;
int result;
if (!iNullable.HasValue)
result = iNullable.GetValueOrDefault(); // result == 0
Метод T GetValueOrDefault(T defaultValue) аналогичен свойству Value. Разница лишь в том, что он не генерирует исключение, а возвращает значение аргумента defaultValue, если отсутствует значение нижележащего типа:
int? iNullable = ....;
int result;
if (!iNullable.HasValue)
result = iNullable.GetValueOrDefault(62); // result == 62
Для Nullable<T> определены оператор неявного преобразования из T в Nullable<T> и явного преобразования из Nullable<T> в T.
Присвоение значений T в переменные Nullable<T> возможно напрямую:
Nullable<int> nullableInt;
nullableInt = 62;
Для того чтобы записать значение из Nullable<T> в переменную T, потребуется выполнить явное приведение. Если в Nullable<T> будет отсутствовать нижележащее значение (Nullable<T>.HasValue - false), при выполнении явного приведения будет сгенерировано исключение типа InvalidOperationException.
Пример:
Nullable<int> nullableIntLhs = 62;
int resultLhs = (int)nullableIntLhs; // OK, 62
Nullable<int> nullableIntRhs = null;
int resultRhs = (int)nullableIntRhs; // InvalidOperationException
Этот факт может сбить с толку с учётом того, что мы обсуждали выше и что следующий код успешно компилируется:
Nullable<int> nullableInt = null;
Однако следует помнить, что Nullable<int> — значимый тип. Следовательно, null здесь всего лишь синтаксический сахар. В данном случае переменная nullableInt будет проинициализирована значением default(Nullable<int>).
Все приведённые ниже переменные будут иметь одинаковые значения:
Nullable<int> nInt1 = null;
Nullable<int> nInt2 = new Nullable<int>();
Nullable<int> nInt3 = default(Nullable<int>);
int? nInt4 = null;
int? nInt5 = new int?();
int? nInt6 = default(int?);
Более очевидным это становится, если посмотреть IL код, где для инициализации всех переменных явно используется одно и то же значение:
IL_0001: ldloca.s nInt1
IL_0003: initobj valuetype [mscorlib]System.Nullable`1<int32>
IL_0009: ldloca.s nInt2
IL_000b: initobj valuetype [mscorlib]System.Nullable`1<int32>
IL_0011: ldloca.s nInt3
IL_0013: initobj valuetype [mscorlib]System.Nullable`1<int32>
IL_0019: ldloca.s nInt4
IL_001b: initobj valuetype [mscorlib]System.Nullable`1<int32>
IL_0021: ldloca.s nInt5
IL_0023: initobj valuetype [mscorlib]System.Nullable`1<int32>
IL_0029: ldloca.s nInt6
IL_002b: initobj valuetype [mscorlib]System.Nullable`1<int32>
Упаковка значений Nullable<T> обладает рядом особенностей:
Если для T определены унарные или бинарные операторы (например, '+', '-'), то для Nullable<T> действует следующее правило:
Таблица результатов:
Для операторов сравнения больше/меньше ('<', '<=', '>', '>='):
Таблица результатов:
Оператор равенства ('=='):
Таблица результатов:
Оператор неравенства ('!='):
Таблица результатов:
Оператор '&':
Таблица результатов:
Оператор '|':
Таблица результатов:
0