Почему «ноль» присутствует в C # и Java? - PullRequest
71 голосов
/ 07 октября 2008

Мы заметили, что множество ошибок в нашем программном обеспечении, разработанном в C # (или Java), вызывают исключение NullReferenceException.

Есть ли причина, по которой слово "null" даже включено в язык?

В конце концов, если бы не было "null", у меня не было бы ошибки, верно?

Другими словами, какая функция в языке не может работать без нуля?

Ответы [ 25 ]

3 голосов
/ 07 октября 2008

В некоторых ситуациях null - хороший способ показать, что ссылка не была инициализирована. Это важно в некоторых сценариях.

Например:

MyResource resource;
try
{
  resource = new MyResource();
  //
  // Do some work
  //
}
finally
{
  if (resource != null)
    resource.Close();
}

В большинстве случаев это достигается использованием с использованием оператора . Но шаблон все еще широко используется.

Что касается вашего исключения NullReferenceException, причину таких ошибок часто легко устранить, внедряя стандарт кодирования, где все параметры проверяются на достоверность. В зависимости от характера проекта я считаю, что в большинстве случаев достаточно проверить параметры на открытых членах. Если параметры не находятся в ожидаемом диапазоне, выдается ArgumentException какого-либо рода или возвращается результат ошибки, в зависимости от используемого шаблона обработки ошибок.

Проверка параметров сама по себе не устраняет ошибки, но любые возникающие ошибки легче обнаружить и исправить на этапе тестирования.

Как примечание, Андерс Хейлсберг упомянул отсутствие ненулевого применения как одну из самых больших ошибок в спецификации C # 1.0 и то, что включить ее сейчас "сложно".

Если вы по-прежнему считаете, что статическое ненулевое значение ссылки имеет большое значение, вы можете проверить язык spec # . Это расширение C #, где ненулевые ссылки являются частью языка. Это гарантирует, что ссылка, помеченная как ненулевая, никогда не может иметь нулевую ссылку.

3 голосов
/ 07 октября 2008

«Нуль» включен в язык, потому что у нас есть типы значений и ссылочные типы. Это, вероятно, побочный эффект, но, думаю, хороший. Это дает нам много возможностей для эффективного управления памятью.

Почему у нас ноль? ...

Типы значений хранятся в «стеке», их значения находятся непосредственно в этом фрагменте памяти (т. Е. Int x = 5 означает, что в ячейке памяти для этой переменной содержится «5»).

С другой стороны, у ссылочных типов в стеке есть «указатель», указывающий на значение фактическое в куче (т. Е. Строка x = "ello" означает, что блок памяти в стеке содержит только адрес, указывающий на фактическое значение в куче).

Нулевое значение просто означает, что наше значение в стеке не указывает на какое-либо фактическое значение в куче - это пустой указатель.

Надеюсь, я объяснил это достаточно хорошо.

2 голосов
/ 07 октября 2008

В одном ответе упоминалось, что в базах данных есть нули. Это правда, но они очень отличаются от нуля в C #.

В C # нули - это маркеры для ссылки, которая ни на что не ссылается.

В базах данных нули - это маркеры для ячеек значений, которые не содержат значения. Под ячейками значений я обычно имею в виду пересечение строки и столбца в таблице, но концепция ячеек значений может быть расширена за пределы таблиц.

Разница между этими двумя кажется на первый взгляд незначительной. Но это не так.

2 голосов
/ 07 октября 2008

Нуль, поскольку он доступен в C # / C ++ / Java / Ruby, лучше всего рассматривать как странность какого-то неясного прошлого (Алголь), которое каким-то образом сохранилось до наших дней.

Вы используете его двумя способами:

  • Чтобы объявить ссылки без их инициализации (плохо).
  • Для обозначения необязательности (ОК).

Как вы уже догадались, 1) это то, что вызывает у нас бесконечные проблемы в распространенных императивных языках и должно было быть давно запрещено, 2) это действительно существенная особенность.

Существуют языки, которые избегают 1), не предотвращая 2).

Например, OCaml - это такой язык.

Простая функция, возвращающая постоянно увеличивающееся целое число, начиная с 1:

let counter = ref 0;;
let next_counter_value () = (counter := !counter + 1; !counter);;

А в отношении возможности:

type distributed_computation_result = NotYetAvailable | Result of float;;
let print_result r = match r with
    | Result(f) -> Printf.printf "result is %f\n" f
    | NotYetAvailable -> Printf.printf "result not yet available\n";;
1 голос
/ 07 октября 2008

Я удивлен, что никто не говорил о базах данных для их ответа. Базы данных имеют пустые поля, и любой язык, который будет получать данные из БД, должен это обрабатывать. Это означает наличие нулевого значения.

На самом деле, это так важно, что для базовых типов, таких как int, вы можете сделать их обнуляемыми!

Также рассмотрите возвращаемые значения из функций, что если вы хотите, чтобы функция делила пару чисел, и знаменатель мог быть равен 0? Единственный «правильный» ответ в таком случае будет нулевым. (Я знаю, что в таком простом примере исключение, вероятно, будет лучшим вариантом ... но могут быть ситуации, когда все значения верны, но действительные данные могут дать неверный или неисчислимый ответ. Не уверен, что исключение следует использовать в таких случаи ...)

1 голос
/ 07 октября 2008

Я не могу говорить с вашей конкретной проблемой, но похоже, что проблема не в существовании нуля. Null существует в базах данных, вам нужно каким-то образом учесть это на уровне приложения. Не думаю, что это единственная причина, по которой он существует в .net. Но я полагаю, что это одна из причин.

1 голос
/ 07 октября 2008

Помимо ВСЕХ упомянутых выше причин, NULL необходим, когда вам нужен заполнитель для еще не созданного объекта. Например. если у вас есть круговая ссылка между парой объектов, то вам нужен ноль, так как вы не можете создать оба экземпляра одновременно.

class A {
  B fieldb;
}

class B {
  A fielda;
}

A a = new A() // a.fieldb is null
B b = new B() { fielda = a } // b.fielda isnt
a.fieldb = b // now it isnt null anymore

Редактировать: Вы можете вытащить язык, который работает без нулей, но он определенно не будет объектно-ориентированным языком. Например, пролог не имеет нулевых значений.

0 голосов
/ 02 апреля 2014

Функция, которая не может работать без нуля, может отображать «отсутствие объекта».

Отсутствие объекта является важной концепцией. В объектно-ориентированном программировании это необходимо для представления необязательной ассоциации между объектами: объект A может быть присоединен к объекту B, или A может не иметь объекта B. Без нуля мы все еще можем эмулировать это: например, мы можем использовать список объектов, чтобы связать B с A. Этот список может содержать один элемент (один B) или быть пустым. Это несколько неудобно и ничего не решает. Код, который предполагает наличие B, такой как aobj.blist.first().method(), будет взорван аналогично исключению нулевой ссылки: (если blist пусто, каково поведение blist.first()?)

Говоря о списках, null позволяет завершить связанный список. ListNode может содержать ссылку на другой ListNode, который может быть нулевым. То же самое можно сказать и о других структурах динамических множеств, таких как деревья. Null позволяет вам иметь обычное двоичное дерево, у которого конечные узлы отмечены нулевыми дочерними ссылками.

Списки и деревья могут быть построены без нуля, но они должны быть круглыми, или же бесконечными / ленивыми. Это, вероятно, будет рассматриваться как неприемлемое ограничение для большинства программистов, которые предпочли бы иметь выбор при проектировании структур данных.

Боли, связанные с нулевыми ссылками, такие как нулевые ссылки, возникающие случайно из-за ошибок и вызывающих исключения, частично являются следствием статической системы типов, которая вводит нулевое значение в каждый тип: есть пустая строка, нулевое целое число, нулевой виджет, ...

В динамически типизированном языке может существовать один нулевой объект, который имеет свой собственный тип. Результатом этого является то, что у вас есть все представительные преимущества нуля, плюс большая безопасность. Например, если вы напишите метод, который принимает параметр String, то вам гарантируется, что этот параметр будет строковым объектом, а не нулевым. В классе String нет нулевой ссылки: то, что известно как String, не может быть нулевым объектом. Ссылки не имеют типа в динамическом языке. Место хранения, такое как член класса или параметр функции, содержит значение, которое может быть ссылкой на объект. Этот объект имеет тип, а не ссылку.

Таким образом, эти языки обеспечивают чистую, более или менее математически чистую модель «нуля», а затем статические превращают ее в своего рода монстра Франкенштейна.

0 голосов
/ 22 февраля 2013

Извините, что ответил на четыре года позже, я удивлен, что ни один из ответов до сих пор не ответил на первоначальный вопрос таким образом:

Такие языки, как C # и Java , такие как C и другие языки до них, имеют null, так что программист может писать быстрый оптимизированный код с помощью эффективное использование указателей.


  • Низкоуровневое представление

Сначала немного истории. Причина, по которой null был изобретен, заключается в эффективности. При выполнении низкоуровневого программирования на ассемблере абстракция отсутствует, у вас есть значения в регистрах, и вы хотите максимально использовать их. Определение нуля как значения , а не действительного указателя является отличной стратегией для представления либо объекта, либо ничего .

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

  • Общий вид.

Семантически, null ни в коем случае не является необходимым для языков программирования. Например, в классических функциональных языках, таких как Haskell или в семействе ML, нет нуля, а есть типы с именем Maybe или Option. Они представляют собой более высокоуровневую концепцию необязательного значения , не имея никакого отношения к тому, как будет выглядеть сгенерированный код сборки (это будет задачей компилятора).

И это тоже очень полезно, потому что это позволяет компилятору ловить больше ошибок , а это значит меньше NullReferenceExceptions.

  • Объединяя их

В отличие от этих языков программирования очень высокого уровня, C # и Java допускают возможное значение null для каждого ссылочного типа (что является другим именем для типа , который в конечном итоге будет реализован с использованием указатели ).

Это может показаться плохим, но что хорошо в этом, так это то, что программист может использовать знания о том, как он работает под капотом, для создания более эффективного кода (даже если в языке есть сборка мусора).

Это причина, по которой null до сих пор существует в языках: компромисс между необходимостью общей концепции необязательного значения и постоянной потребностью в эффективности.

0 голосов
/ 06 декабря 2011

Если инфраструктура позволяет создавать массив некоторого типа без указания того, что следует делать с новыми элементами, этот тип должен иметь некоторое значение по умолчанию. Для типов, которые реализуют изменяемую ссылочную семантику (*), в общем случае нет разумного значения по умолчанию. Я считаю слабым местом фреймворка .NET то, что нет способа указать, что невиртуальный вызов функции должен подавлять любую нулевую проверку. Это позволило бы неизменным типам, таким как String, вести себя как типы значений, возвращая разумные значения для свойств, таких как Length.

(*) Обратите внимание, что в VB.NET и C # изменяемая семантика ссылок может быть реализована с помощью классов или структур; тип структуры будет реализовывать изменяемую семантику ссылок, выступая в качестве прокси для обернутого экземпляра объекта класса, на который он хранит неизменную ссылку.

Было бы также полезно, если бы можно было указать, что у класса должна быть ненулевая изменяемая семантика типа значения (подразумевается, что - как минимум - создание экземпляра поля этого типа создаст новый экземпляр объекта с использованием конструктора по умолчанию) и что копирование поля этого типа создаст новый экземпляр путем копирования старого (рекурсивно обрабатывая любые вложенные классы типа значения).

Неясно, однако, сколько именно поддержки должно быть встроено в структуру для этого. Наличие самой структуры, распознающей различия между изменяемыми типами значений, изменяемыми ссылочными типами и неизменяемыми типами, позволило бы классам, которые сами содержат ссылки на смесь изменяемых и неизменяемых типов извне классов, эффективно избегать создания ненужных копий глубоко неизменяемых объектов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...