Почему C # позволяет «выбрасывать ноль»?

85

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

Почему это разрешено?

throw null;

К счастью, в этом фрагменте ex не имеет значения null, но может ли это когда-нибудь быть?

try
{
  throw null;
}
catch (Exception ex)
{
  //can ex ever be null?

  //thankfully, it isn't null, but is
  //ex is System.NullReferenceException
}
шутка
источник
8
Вы уверены, что не видите исключение .NET (пустая ссылка) поверх вашего собственного броска?
micahtan 03
4
Вы могли подумать, что компилятор, по крайней мере, выдаст предупреждение о попытке напрямую "выбросить null;"
Энди Уайт,
Нет, я не уверен. Фреймворк вполне может пытаться что-то сделать с моим объектом, и когда он равен нулю, фреймворк выдает "исключение нулевой ссылки" ... но в конечном итоге я хочу быть уверен, что "ex" никогда не может быть нулевым
шутка
1
@Andy: предупреждение может иметь смысл для других ситуаций на языке, но для throwоператора, цель которого в первую очередь генерировать исключение, предупреждение не добавляет большого значения.
mmx 03
@Mehrdad - да, но я сомневаюсь, что какой-либо разработчик намеренно "выбросит ноль" и захочет увидеть исключение NullReferenceException. Возможно, это должна быть полная ошибка сборки, например, неправильное сравнение = в операторе if.
Энди Уайт,

Ответы:

96

Поскольку спецификация языка ожидает там выражения типа System.Exception(следовательно, nullявляется допустимым в этом контексте) и не ограничивает это выражение ненулевым значением. В общем, невозможно определить, равно ли значение этого выражения null. Это должно было бы решить проблему остановки. Среда выполнения в nullлюбом случае должна будет заняться этим делом. Видеть:

Exception ex = null;
if (conditionThatDependsOnSomeInput) 
    ex = new Exception();
throw ex; 

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

Заявление об ограничении ответственности (до того, как меня ударит Эрик Липперт): это мое собственное предположение о причинах этого дизайнерского решения. Я, конечно, не был на дизайнерской встрече;)


Ответ на ваш второй вопрос, может ли переменная выражения, пойманная в предложении catch, когда-либо иметь значение NULL: хотя спецификация C # nullничего не говорит о том, могут ли другие языки вызывать распространение исключения, она определяет способ распространения исключений:

Предложения catch, если таковые имеются, проверяются в порядке появления, чтобы найти подходящий обработчик исключения. Первое предложение catch , определяющее тип исключения или базовый тип типа исключения, считается совпадением. Общее предложение catch считается подходящим для любого типа исключения. [...]

Ибо nullжирное заявление неверно. Итак, хотя чисто на основе того, что говорится в спецификации C #, мы не можем сказать, что базовая среда выполнения никогда не будет генерировать null, мы можем быть уверены, что даже в этом случае это будет обрабатываться только общим catch {}предложением.

Для реализации C # в CLI мы можем обратиться к спецификации ECMA 335. Этот документ определяет все исключения, которые CLI генерирует внутренне (ни одно из которых не является null), и упоминает, что определенные пользователем объекты исключения генерируются throwинструкцией. Описание этой инструкции практически идентично инструкции C # throw(за исключением того, что она не ограничивает тип объекта System.Exception):

Описание:

throwИнструкция бросает объект исключения (типа O) в стеке и опустошает стек. Для получения подробной информации о механизме исключений см. Раздел I.
[Примечание: CLI разрешает генерирование любого объекта, CLS описывает конкретный класс исключения, который должен использоваться для языковой совместимости. конец примечания]

Исключения:

System.NullReferenceExceptionвыбрасывается, если objесть null.

Правильность:

Правильный CIL гарантирует, что объект всегда является либо nullссылкой на объект (т. Е. Типом O).

Я считаю, что этого достаточно, чтобы сделать вывод, что пойманных исключений никогда не бывает null.

ммх
источник
Я полагаю, что лучшее, что он мог бы сделать, - это запретить такие явные фразы, как throw null;.
FrustratedWithFormsDesigner
7
На самом деле, вполне возможно (в CLR) создать объект, который не наследуется от System.Exception. Насколько мне известно, вы не можете сделать это на C #, но это можно сделать с помощью IL, C ++ / CLR и т. Д. См. Msdn.microsoft.com/en-us/library/ms404228.aspx для получения дополнительной информации.
технофил
@technophile: Да. Я говорю о языке здесь. В C # невозможно генерировать исключение других типов, но можно поймать его с помощью универсального catch { }предложения.
mmx 03
1
Хотя для компилятора не имеет смысла отказываться принимать throwаргумент, аргумент которого может быть нулевым значением, это не означает, что throw nullэто должно быть законным. Компилятор может настоять на том, чтобы throwаргумент имел различимый тип класса. Выражение вроде (System.InvalidOperationException)nullдолжно быть действительным во время компиляции (его выполнение должно вызывать a NullReferenceException), но это не означает, что нетипизированное nullдолжно быть приемлемо.
supercat
@supercat: Это правда, но нуль в этом случае неявно типизируется как Exception (потому что он используется в контексте, где требуется Exception). null недействителен во время выполнения, поэтому создается исключение NullReferenceException (как указано в ответе Anon). Выброшенное исключение не является тем, что указано в операторе throw.
Джон Б. Ламбе
29

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

Попытка выбросить nullобъект приводит к (совершенно не связанному) исключению нулевой ссылки.

Спрашивать, почему вам разрешено бросать, все nullравно что спрашивать, почему вам разрешено это делать:

object o = null;
o.ToString();
Анон.
источник
5

Взято отсюда :

Если вы используете это выражение в своем коде C #, оно вызовет исключение NullReferenceException. Это связано с тем, что оператору throw в качестве единственного параметра требуется объект типа Exception. Но в моем примере этот самый объект является нулевым.

Фитцхак Ицхаки
источник
5

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

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

Брайан Кеннеди
источник
3
Интересно. Не могли бы вы опубликовать какой-нибудь код или, может быть, ускользнуть от того, что находится в вашей глубине ниже, что делает это?
Питер Лиллевольд
Я не могу сказать вам, является ли это ошибкой CLR ... трудно отследить, что в кишках .NET выдало null и почему, учитывая, что вы не получаете Exception со стеком вызовов или какие-либо другие подсказки, чтобы продолжить. Я просто знаю, что мой внешний улов теперь проверяет нулевой аргумент Exception перед его использованием.
Брайан Кеннеди
3
Я подозреваю, что это ошибка CLR или плохой непроверяемый код. Правильный CIL будет генерировать либо ненулевой объект, либо нулевой (который становится NullReferenceException).
Деми
Я также использую службу, которая возвращает нулевое исключение. Вы когда-нибудь выясняли, как генерируется нулевое исключение?
themiDdlest
2

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

Стив Купер
источник
2

Пытаюсь ответить «… слава богу, ex не является нулем, но может ли это когда-нибудь быть?»:

Поскольку мы, вероятно, не можем генерировать исключения с нулевым значением, предложению catch также никогда не придется перехватывать нулевое исключение. Таким образом, ex никогда не может быть нулевым.

Теперь я вижу, что этот вопрос, по сути, уже задавался .

Питер Лиллевольд
источник
@Brian Kennedy говорит нам в своем комментарии выше, что мы можем поймать null.
ProfK
@ Tvde1 - Думаю, разница в том, что да, вы можете выполнить throw null. Но это не вызовет исключения с нулевым значением . Скорее, поскольку throw nullпытается вызвать методы для нулевой ссылки, это впоследствии заставляет среду выполнения генерировать ненулевой экземпляр NullReferenceException.
Питер
1

Помните, что исключение включает подробную информацию о том, где возникло исключение. Поскольку конструктор не знает, куда он должен быть брошен, имеет смысл только то, что метод throw вводит эти детали в объект в точке, где находится выброс. Другими словами, среда CLR пытается ввести данные в значение null, что вызывает исключение NullReferenceException.

Не уверен, что именно это происходит, но это объясняет феномен.

Предполагая, что это правда (и я не могу придумать лучшего способа получить значение ex равным null, чем throw null;), это будет означать, что ex не может быть нулевым.

Гуванте
источник
0

В более старом С #:

Рассмотрим этот синтаксис:

public void Add<T> ( T item ) => throw (hashSet.Add ( item ) ? null : new Exception ( "The item already exists" ));

Я думаю, что это намного короче:

public void Add<T> ( T item )
{
    if (!hashSet.Add ( item ))
        throw new Exception ( "The item already exists" );
}
Арутюн Энфенджян
источник
Что это говорит нам?
bornfromanegg
Кстати, я не уверен, каково ваше определение короче, но ваш второй пример содержит меньше символов.
bornfromanegg
@bornfromanegg no 1-й встроен, поэтому он определенно короче. Я пытался сказать, что вы можете выдать ошибку в зависимости от условия. Традиционно u поступил бы как 2-й пример, но поскольку «throw null» ничего не бросает, u может встроить второй пример, чтобы он выглядел как 1-й пример. Также в первом примере на 8 символов меньше, чем во втором
Арутюн Энфенджян
«Throw null» вызывает исключение NullReference. Это означает, что ваш первый пример всегда будет вызывать исключение.
bornfromanegg
да, к сожалению, сейчас. Но не раньше
Арутюн Энфенджян