null – это не false

В языке C# «null» используется для указания «отсутствия» значения или «некорректного» значения. Любой ссылочный тип может принимать значение «null», т.е. ссылка на самом деле никуда не указывает. А для любого «нормального» значимого типа существуют соответствующий «nullable» значимый тип, который может быть равен null.

Но две эти концепции реализованы совершенно по-разному. Ссылка обычно представляет собой 32-х или 64-х битовое значение. Как мы обсуждали ранее, это значение должно логически рассматриваться, как некоторый «непрозрачный» (opaque) дескриптор, о котором знает только сборщик мусора, но на практике оно содержит смещение в виртуальном адресном пространстве процесса, по которому располагается объект в управляемой куче. При этом нуль зарезервирован для представления пустых ссылок, поскольку операционная система всегда резервирует первые несколько страниц виртуальной памяти как недоступные. Нет никаких шансов, что по какой-то случайности нулевой адрес будет являться корректным адресом в куче.

Однако nullable значимые типы просто представляют собой экземпляр значимого типа, плюс булев флаг, указывающий на то, можно ли рассматривать этот объект в качестве значения или же он может иметь значение null. Это всего лишь синтаксический сахар для передачи этого флага. Причина заключается в том, что значимые типы не содержат «специального» значения со специальной семантикой; byte может содержать 256 допустимых значений, каждое из которых является корректным, поэтому для представления nullable byte нужно дополнительное хранилище.

Некоторые языки позволяют трактовать пустое (null value) значение значимых, ссылочных типов или и тех, и других, в качестве булевого значения. В языке С, например, можно сделать следующее:

int* x = whatever();
if (x) ...

Что будет аналогично выражению «if (x != null)». То же самое относится и к nullable значимым типам; некоторые языки позволяют рассматривать null, как «false».

Проектировщики языка C# рассмотрели данные возможности и решили их отвергнуть. Во-первых, подобное поведение может сбивать с толку и быть потенциальным источником ошибок. Во-вторых, трактовать значение null, что означает «значение отсутствует» или «значение неизвестно», как «это значение логически ложно», кажется слишком самонадеянным.

В частности, мы хотим, чтобы nullable bool содержал три значения: true, false и null, а не три следующих: true, false и еще-один-вид-значения false. Рассмотрение null, в качестве false может приводить к ряду неоднозначностей. Предположим, мы реализовали именно такое поведение, и, предположим, x – это переменная типа nullable bool, равная null:

if (x)
Foo();
if (!x)
Bar();

Ни Foo, ни Bar не будут выполнены, поскольку «НЕ null» также равно null. (Ответом на вопрос: «какое противоположное значение у неизвестного значения?» является «неизвестное значение».) Не странно ли, что оба условия x и !x ложны? Аналогично, выражение if (x | !x) также будет ложным, что также кажется странным.

Решение всех этих проблем сводится, прежде всего, к тому, чтобы не рассматривать null в качестве false.

В следующий раз мы рассмотрим разные аспекты определений истинности: что делать с этими пользовательскими операторами «true» и «false»?

Оригинал статьи