﻿# Nullable value types

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_, можно следующим образом:

```cpp
Nullable<int> nullableInt;
```

Однако чаще используют сокращённый вариант записи:

```cpp
int? nullableInt;
```

Для представленных выше объявлений C\# из кода будет сгенерирован одинаковый IL код\.

Как значения нижележащего типа, так и значение _null_ записываются в переменную через простое присвоение:

```cpp
int? nullableIntLhs = 62;
int? nullableIntRhs = null;
```

### Свойства

Свойство _HasValue_ позволяет выяснить, содержит ли nullable value type переменная значение нижележащего типа:

```cpp
int? iNullable = 62;
int result;
if (iNullable.HasValue)
  result = iNullable.Value;
else
  result = -1;
// result == 62
```

Кроме вызова свойства _HasValue_, проверить на наличие значение можно через сравнение с _null_\. Следующие проверки эквивалентны, для них генерируется одинаковый IL код:

```cpp
int? nullableInt = null;
bool hasValue1 = nullableInt.HasValue;
bool hasValue2 = nullableInt != null;
```

Свойство_ Value_ возвращает значение нижележащего типа, если оно имеется \(_Nullable<T\>\.HasValue_ \- _true_\)\. В противном случае будет выброшено исключение типа _InvalidOperationException_:

```cpp
int? iNullable = ....;
int result;
if (iNullable.HasValue)
  result = iNullable.Value; // OK
else
  result = iNullable.Value; // InvalidOperationException
```

### Методы

Метод_ T GetValueOrDefault\(\) _аналогичен свойству _Value_ с тем отличием, что он не генерирует исключение, а возвращает _default_ значение типа _T_, если отсутствует значение нижележащего типа:

```cpp
int? iNullable = ....;
int result;
if (!iNullable.HasValue)
  result = iNullable.GetValueOrDefault(); // result == 0
```

Метод_ T GetValueOrDefault\(T defaultValue\) _аналогичен свойству _Value\._ Разница лишь в том, что он не генерирует исключение, а возвращает значение аргумента _defaultValue_, если отсутствует значение нижележащего типа:

```cpp
int? iNullable = ....;
int result;
if (!iNullable.HasValue)
  result = iNullable.GetValueOrDefault(62); // result == 62
```

### Операторы преобразования

Для _Nullable<T\>_ определены оператор неявного преобразования из _T_ в _Nullable<T\>_ и явного преобразования из _Nullable<T\>_ в _T_\.

Присвоение значений _T_ в переменные _Nullable<T\>_ возможно напрямую:

```cpp
Nullable<int> nullableInt;
nullableInt = 62;
```

Для того чтобы записать значение из _Nullable<T\>_ в переменную _T_, потребуется выполнить явное приведение\. Если в _Nullable<T\>_ будет отсутствовать нижележащее значение \(_Nullable<T\>\.HasValue_ \- _false_\), при выполнении явного приведения будет сгенерировано исключение типа _InvalidOperationException_\.

Пример:

```cpp
Nullable<int> nullableIntLhs = 62;
int resultLhs = (int)nullableIntLhs; // OK, 62
Nullable<int> nullableIntRhs = null;
int resultRhs = (int)nullableIntRhs; // InvalidOperationException
```

## Особенности использования nullable value types

### Nullable<T\> не может иметь значения null

Этот факт может сбить с толку с учётом того, что мы обсуждали выше и что следующий код успешно компилируется:

```cpp
Nullable<int> nullableInt = null;
```

Однако следует помнить, что _Nullable<int\>_ — значимый тип\. Следовательно, _null_ здесь всего лишь синтаксический сахар\. В данном случае переменная _nullableInt_ будет проинициализирована значением _default\(Nullable<int\>\)_\.

Все приведённые ниже переменные будут иметь одинаковые значения:

```cpp
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 код, где для инициализации всех переменных явно используется одно и то же значение:

```cpp
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\>_ обладает рядом особенностей:

* если _Nullable<T\>\.HasValue — true_, при упаковке пакуется не сам экземпляр _Nullable<T\>_, а значение нижележащего типа — _Nullable<T\>\.Value_;
* если _Nullable<T\>\.HasValue_ — _false_, результатом упаковки будет значение _null_;
* если распаковывается значение _null_, результатом будет значение _default\(Nullable<T\>\)_\.

### Операторы, определённые для T

Если для _T_ определены унарные или бинарные операторы \(например, '\+', '\-'\), то для _Nullable<T\>_ действует следующее правило:

* если значение хотя бы одного операнда — _null_, результат — _null_;
* если значение обоих операндов не _null_, результирующее значение — результат работы оператора для нижележащих значений операндов \(_Nullable<T\>\.Value_\)\.

Таблица результатов:

![NullableValueTypes_ru/image1.png](https://import.viva64.com/docx/blog/NullableValueTypes_ru/image1.png)

Для операторов сравнения больше/меньше \('<', '<\=', '\>', '\>\='\):

* если хотя бы один из операндов — _null_, результат — _false_;
* если значение обоих операндов — не _null_, результирующие значение — результат работы оператора для нижележащих значений операндов \(_Nullable<T\>\.Value_\)\.

Таблица результатов:

![NullableValueTypes_ru/image2.png](https://import.viva64.com/docx/blog/NullableValueTypes_ru/image2.png)

Оператор равенства \('\=\='\):

* если оба операнда — _null_, результат — _true_;
* если один операнд — _null_, другой — не _null_, результат — _false_;
* если оба операнда — не _null_, результирующее значение — результат работы оператора '\=\=' для значений нижележащего типа\.

Таблица результатов:

![NullableValueTypes_ru/image3.png](https://import.viva64.com/docx/blog/NullableValueTypes_ru/image3.png)

Оператор неравенства \('\!\='\):

* если оба операнда — _null_, результат — _false_;
* если один операнд — _null_, другой — не _null_, результат — _true_;
* если оба операнда — не _null_, результирующее значение — результат работы оператора '\!\=' для значений нижележащего типа\.

Таблица результатов:

![NullableValueTypes_ru/image4.png](https://import.viva64.com/docx/blog/NullableValueTypes_ru/image4.png)

Оператор '&':

* если оба операнда — _null_, результат — _null_;
* если один операнд — _null_, другой — _true_, результат — _null_;
* если один операнд — _null_, другой — _false_, результат — _false_;
* если оба операнда — не _null_, результирующее значение — _lhs & rhs_\.

Таблица результатов:

![NullableValueTypes_ru/image5.png](https://import.viva64.com/docx/blog/NullableValueTypes_ru/image5.png)

Оператор '\|':

* если оба операнда — _null_, результат — _null_;
* если один операнд — _null_, другой — _false_, результат — _null_;
* если один операнд — _null_, другой — _true_, результат — _true_;
* если оба операнда — не _null_, результирующее значение — _lhs \| rhs_\.

Таблица результатов:

![NullableValueTypes_ru/image6.png](https://import.viva64.com/docx/blog/NullableValueTypes_ru/image6.png)

## Дополнительные ссылки

* [Хорошо ли вы помните nullable value types? Заглядываем "под капот"](https://pvs-studio.ru/ru/blog/posts/csharp/0772/)\.