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


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

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

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

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

25 68

25 ответов:

Андерс Хейлсберг, "c# отец", только что говорил об этом пункте в интервью журналу Computerworld:

например, в системе типов у нас нет разделения между значениями и ссылочными типами и обнуления типов. Это может показаться немного шатким или немного техническим, но в C# ссылочные типы могут быть null, например строки, но типы значений не могут быть null. Было бы неплохо иметь ненулевые ссылочные типы, поэтому вы можете объявить, что "эта строка никогда не может быть null, и я хочу, чтобы вы компилятор, чтобы проверить, что я никогда не могу ударить нулевой указатель здесь".

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

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

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

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

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

Как бы вы предложили убрать понятие ничтожности?

Как и многие вещи в объектно-ориентированном программировании, все это восходит к ALGOL. во всяком случае, это преуменьшение.

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

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

Null также имеет законное место в системе типов, однако, как нечто сродни снизу тип (где object это top тип). В lisp, the Нижний тип -NIL а в скале это Nothing.

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

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

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

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

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

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

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

есть еще много чего, о чем я мог бы рассказать, но я оставлю это там.

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

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

null не привести к возникновению исключительных ситуаций типа NullPointerException...

программистов привести к возникновению исключительных ситуаций типа NullPointerException.

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

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

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

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

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

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

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

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

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

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

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

например:

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

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

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

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

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

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

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

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

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

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

Null, как это доступно в C#/C++/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";;

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

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

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

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

помимо всех уже упомянутых причин, 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

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

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

Я предлагаю:

  1. Ban Null
  2. расширить логические значения: True, False и FileNotFound

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

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

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

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

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

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

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


  • низкоуровневый вид

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

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

  • вид на высоком уровне.

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

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

  • объединение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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