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


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

почему это допускается?

throw null;

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

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

  //thankfully, it isn't null, but is
  //ex is System.NullReferenceException
}
7 67

7 ответов:

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

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

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

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


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

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

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

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

описание:

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

исключения:

System.NullReferenceException бросается, если obj и null.

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

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

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

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

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

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

object o = null;
o.ToString();

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

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

принято от здесь:

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

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

пытаясь ответить "..к счастью, " ex " не является нулевым, но может ли это когда-нибудь быть?":

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

теперь я вижу, что этот вопрос фактически уже попросил.

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

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

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