Speak.Me Учить иностранные слова

C#: типы, допускающие значение Null (Nullable Types)

Ссылочные типы могут иметь значение null, если ссылка не ссылается ни на какой объект. В противоположность им значимые типы не могут иметь значение null.

Чтобы сделать возможным для значимых типов принимать значение null, нужно использовать специальную конструкцию — тип, допускающий значение null (nullable type). Чтобы превратить значимый тип в тип, допускающий значение null, нужно при объявлении переменной непосредственно после указания ее (значимого) типа поставить знак ?:

Структура Nullable<T>

T? преобразуется в System.Nullable<T>Nullable<T> — легкая неизменяемая структура, имеющая только два поля: Value и HasValue.

Таким образом код:

преобразуется в:

Попытка получить значение поля Value если поле HasValue возвращает false приведет к выбрасыванию исключения InvalidOperationException. Метод GetValueOrDefault() вернет значение Value если HasValue возвращает true, в противном случае он вернет new T() или другое, кастомное, значение по умолчанию. Значение по умолчанию для T?null.

Преобразование типов, допускающих значение null

Преобразование из T в T? выполняется автоматически, а преобразование из T? в T требует явного приведения:

Явное приведение объекта, допускающего значение null, эквивалентно вызову его свойства Value. При этом, если HasValue возвращает false, будет выброшено исключение InvalidOperationException.

Приведение к объектному типу и восстановление значения типов, допускающих значение null (Boxing/Unboxing Nullable Values)

Когда T? приводится к объектному типу, объект в хипе будет содержать значение типа T, а не T?. Такого рода оптимизация возможна благодаря тому, что приведенное к объектному типу значение уже является ссылочным типом и может принимать значение null.

Для типов, допускающих значение null, восстановление значения из объектного образа (unboxing) можно сделать с помощью оператора as. При этом, если операция не возможна, будет возвращено значение null.

Поднятие операторов (Operator Lifting)

Для структуры Nullable<T> не определены такие операторы как <, > и даже ==. Несмотря на это выражения с этими операторами будут вполне корректно скомпилированы и выполнены:

Это возможно благодаря тому, что компилятор заимствует или поднимает (lift) операторы у соответствующих значимых типов. Семантически пример выше преобразуется в следующий код:

Т.е. если x и y имеют значения, они сравниваются как int, если нет — выражение вернет false.

Поднятие операторов означает, то можно использовать операторы типа T для типа T?. Можно переопределить операторы для типа T?, чтобы реализовать специальную логику для операций со значением null, однако в большинстве случаев лучше будет использовать стандартную логику компилятора. Поведение компилятора, если один или оба операнда будут иметь значение null, зависит от типа оператора.

Операторы равенства (==, !=)

Поднятые операторы равенства со значениями null ведут себя также как операторы ссылочных типов:

  • два значения null равны
  • если только один операнд имеет значение null — операнды не равны
  • если оба операнда имеют не нулевые значения — сравниваются их значения (свойства Value)

Операторы отношения (<, <=, >=, >)

Операторы отношения исходят из принципа бессмысленности сравнивать значения null: если хотя бы один операнд (или оба) имеет значение null, выражение вернет false:

Все остальные операторы (+, –, *, /, %, &, |, ^, <<, >>, +, ++, —, !, ~)

Все остальные операторы вернут null, если хотя бы один операнд имеет значение null:

Сочетание операторов

Благодаря возможности автоматического приведения T к T? можно сочетать в одном выражении обычные значимые типы и типы, допускающие значение null:

Тип bool? с операторами & и |

Операторы & и | при взаимодействии с операндами типа bool? расценивают null как неизвестное значение. В связи с этим выражение null | true вернет true т.к.:

  • если неизвестное значение false, результат будет true
  • если неизвестное значение true, результат будет true

По тем же причинам выражение null & false вернет false.

Оператор объединения со значением NULL (??)

Оператор ?? называется оператором объединения со значением null. Он может использоваться как с типами, допускающими значение null, так и с ссылочными типами. Оператор вернет свой левый операнд если он не равен null,  в противном случае он вернет правый операнд.