Почему "null" присутствует в С# и Java?

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

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

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

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

Ответы

Ответ 1

Андерс Хейлсберг, "отец С#", просто рассказал об этом в

50% ошибок, с которыми люди сталкиваются сегодня, кодирование с С# в нашей платформе, и то же самое относится к Java в этом отношении, вероятно, являются нулевыми ссылочными исключениями. Если бы у нас была более сильная система типов, которая позволила бы вам сказать, что" этот параметр никогда не может быть нулевым, и вы компилятор, пожалуйста, проверьте это при каждом вызове, выполнив статический анализ кода. Тогда мы могли бы отбросить классы ошибок.

Сайрус Наджмабади, бывший разработчик программного обеспечения в команде С# (теперь работает в Google), обсуждает эту тему в своем блоге: (, , , ). Похоже, что самым большим препятствием для принятия непустых типов является то, что нотация будет нарушать привычки программистов и базу кода. Что-то вроде 70% ссылок на программы С#, скорее всего, заканчивается как непустые.

Если вы действительно хотите, чтобы в С# не был нулевой ссылочный тип, вы должны попытаться использовать который является расширением С#, которое позволяет использовать "!" как знак, не имеющий значения NULL.

static string AcceptNotNullObject(object! s)
{
    return s.ToString();
}

Ответ 2

Nullity является естественным следствием ссылочных типов. Если у вас есть ссылка, она должна ссылаться на некоторый объект - или быть нулевым. Если бы вы запретили недействительность, вам всегда нужно было убедиться, что каждая переменная была инициализирована некоторым непустым выражением - и даже тогда у вас возникнут проблемы, если переменные были прочитаны во время фазы инициализации.

Как бы вы предложили удалить концепцию nullity?

Ответ 3

Как и в объектно-ориентированном программировании, все возвращается к ALGOL. Тони Хоар просто назвал это своей" ошибкой в ​​миллиард долларов ". Во всяком случае, это преуменьшение.

Вот действительно интересный тезис о том, как сделать nullability не стандартным в Java. Параллели с С# очевидны.

Ответ 4

Null в С# в основном переносится с С++, у которого были указатели, которые не указывали на ничего в памяти (точнее, адрес 0x00). В это интервью, Андерс Хейлсберг говорит, что он хотел бы добавить не- нулевые ссылочные типы в С#.

У Null также есть законное место в системе типов, однако, как нечто похожее на нижний тип (где object - это top). В lisp нижний тип NIL, а в Scala - Nothing.

Было бы возможно спроектировать С# без каких-либо нулей, но тогда вам придётся найти приемлемое решение для обычаев, которые обычно имеют для null, например unitialized-value, not-found, default-value, undefined-value и None<T>. Вероятно, было бы меньше усыновления среди программистов на С++ и Java, если бы они так или иначе преуспели в этом. По крайней мере, пока они не увидели, что в программах на С# никогда не было исключений с нулевым указателем.

Ответ 5

Удаление нулевого значения не решит. Для большинства переменных, которые установлены в init, вам понадобится ссылка по умолчанию. Вместо исключений с нулевой ссылкой вы получите неожиданное поведение, потому что переменная указывает на неправильные объекты. По крайней мере, нулевые ссылки быстро ускоряются, а не вызывают неожиданное поведение.

Вы можете посмотреть шаблон нулевого объекта для решения части этой проблемы.

Ответ 6

Null - чрезвычайно мощная функция. Что вы делаете, если у вас нет значения? Это NULL!

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

Я предпочитаю null, поскольку для меня это более точное указание на то, что на самом деле. Если я не могу получить объект из моего уровня персистентности, я хочу null. Мне не нужно пустое значение. Но это я.

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

Есть намного больше, о чем я мог бы продолжить, но я оставлю его там.

Ответ 7

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

Ответ НЕТ. Проблема не в том, что С# допускает null, проблема в том, что у вас есть ошибки, которые проявляются с помощью исключения NullReferenceException. Как уже было сказано, нули имеют цель на языке указывать либо "пустой" ссылочный тип, либо не значение (пустое/ничего/неизвестное).

Ответ 8

Null не вызывает NullPointerExceptions...

Программисты вызывают NullPointerExceptions.

Без нулей мы вернемся к использованию фактического произвольного значения, чтобы определить, что возвращаемое значение функции или метода было неверным. Вы по-прежнему должны проверять возвращаемый -1 (или что-то еще), удаление нулей не волшебным образом разрешит ленивость, а мучительно запутывает его.

Ответ 9

Вопрос может быть истолкован как "Лучше ли иметь значение по умолчанию для каждого типа ссылок (например, String.Empty) или null?". В этом случае я предпочел бы иметь нули, потому что:

  • Я бы не хотел писать по умолчанию конструктор для каждого класса, который я пишу.
  • Мне не нравятся некоторые ненужные память, предназначенная для таких значения по умолчанию.
  • Проверка того, ссылка нулевая относительно дешевле чем сравнение значений.
  • Это очень возможно иметь больше ошибок, которые труднее обнаружить, а не NullReferanceExceptions. Это хорошо иметь такое исключение что ясно указывает на то, что я делая (предполагая) что-то не так.

Ответ 10

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

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

Типы значений хранятся в "стеке", их значение находится непосредственно в этой части памяти (т.е. int x = 5 означает, что в ячейке памяти для этой переменной содержится "5" ).

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

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

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

Ответ 11

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

Например:

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

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

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

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

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

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

Ответ 12

Если вы получаете "NullReferenceException", возможно, вы продолжаете ссылаться на объекты, которые больше не существуют. Это не проблема с "null", это проблема с вашим кодом, указывающим на несуществующие адреса.

Ответ 13

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

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

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

Как вы догадались, 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";;

Ответ 14

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

В С# нули являются маркерами для ссылки, которая не ссылается ни на что.

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

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

Ответ 15

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

Ответ 16

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

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

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

Ответ 17

Помимо всех упомянутых причин, NULL необходим, когда вам нужен заполнитель для еще не созданного объекта. Например. если у вас есть круговая ссылка между парой объектов, тогда вам нужно 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

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

Ответ 18

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

Ответ 19

Я предлагаю:

  • Запретить Null
  • Расширение булевых: True, False и FileNotFound

Ответ 20

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

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

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

Ответ 21

  • Явное обнуление, хотя это редко необходимо. Возможно, это можно рассматривать как форму защитного программирования.
  • Используйте его (или структуру NULL (T) для не-nullables) в качестве флага для указания отсутствующего поля при сопоставлении полей из одного источника данных (байтового потока, таблицы базы данных) в объект. Это может стать очень громоздким для создания логического флага для каждого возможного поля с возможностью NULL, и может оказаться невозможным использовать дозорные значения, такие как -1 или 0, когда все значения в диапазоне этого поля действительны. Это особенно удобно, когда есть много полей.

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

Ответ 22

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

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

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

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

Ответ 23

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

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


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

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

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

  • Высокое представление.

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

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

  • Приведение их вместе

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

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

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

Ответ 24

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

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

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

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

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

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

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

Ответ 25

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