Чому C # дозволяє вам "кидати нуль"?


85

Під час написання якогось особливо складного коду обробки винятків хтось запитав, чи не потрібно переконуватись, що об’єкт винятку не має значення null? І я сказав, звичайно, ні, але потім вирішив спробувати. Мабуть, ви можете кинути нуль, але це все одно десь перетворено на виняток.

Чому це дозволено?

throw 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

4
Можна подумати, що компілятор, принаймні, підніме попередження про спробу "відкинути null;"
Andy White

Ні, я не впевнений. Фреймворк цілком може намагатися щось зробити із моїм об’єктом, і коли він має значення null, фреймворк видає "Null Reference Exception" .. але в кінцевому рахунку, я хочу бути впевненим, що "ex" ніколи не може бути null
quip

1
@Andy: Попередження може мати сенс для інших ситуацій у мові, але для throwзаяви, метою якої є передусім виняток, попередження не додає великої цінності.
Мехрдад Афшарі

@Mehrdad - так, але я сумніваюся, чи хтось розробник навмисно "кине нуль" і хоче побачити NullReferenceException. Можливо, це повинна бути повна помилка збірки, наприклад неправильне = порівняння в операторі if.
Andy White

Відповіді:


96

Оскільки специфікація мови очікує там виразу типу System.Exception(отже, nullє допустимим у цьому контексті) і не обмежує цей вираз ненульовим. Загалом, жодним чином він не може визначити, чи є значення цього виразу nullчи ні. Це повинно було б вирішити проблему зупинки. Виконавцю все nullодно доведеться розібратися зі справою. Подивитися:

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

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

Застереження (до того, як Ерік Ліпперт отримає мене ляпас): Це моє власне припущення щодо міркувань, що лежать в основі цього дизайнерського рішення. Звичайно, я не був на зустрічі дизайнерів;)


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

Застереження про вилов, якщо такі є, перевіряються з метою виявлення відповідного обробника для виключення. Перше речення 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 { }речення.
Мехрдад Афшарі

1
Хоча не має сенсу для компілятора відмовлятися прийняти throwаргумент, чий аргумент може бути нульовим значенням, це не означає, що throw nullце повинно бути законним. Компілятор може наполягати на тому, щоб throwаргумент мав помітний тип класу. Вираз like (System.InvalidOperationException)nullповинен бути дійсним під час компіляції (його виконання повинно викликати a NullReferenceException), але це не означає, що нетипізований nullповинен бути прийнятним.
supercat

@supercat: Це правда, але нуль у цьому випадку неявно вводиться як Exception (оскільки він використовується в контексті, де вимагається Exception). null не є дійсним під час виконання, тому NullReferenceException видається (як зазначено у відповіді Anon.). Виключений виняток - це не той, що в операторі 'throw'.
Джон Б. Ламб,

29

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

Спроба кинути nullоб’єкт призводить до (повністю не пов’язаного) нульового виключення посилань.

Запитати, чому вам дозволено кидати, nullвсе одно, що запитати, чому вам це дозволено:

object o = null;
o.ToString();

5

Взято звідси :

Якщо ви використовуєте цей вираз у коді C #, він видасть NullReferenceException. Це тому, що оператору throw потрібен об’єкт типу Exception як єдиний параметр. Але саме цей об'єкт є нульовим у моєму прикладі.


5

Незважаючи на те, що в C # може бути неможливо кинути null, оскільки кидок виявить це і перетворить його на NullReferenceException, Є можливість отримати null ... Я випадково отримую це прямо зараз, що спричиняє мій улов (який не був очікуючи, що `` ex '' буде нульовим) матиме виняток нульового посилання, який потім призведе до того, що мій додаток помре (оскільки це був останній улов).

Отже, хоча ми не можемо викинути нуль із C #, потойбіччя може скинути нуль, тому ваш крайній улов (Виняток ex) краще бути готовим отримати його. Просто FYI.


3
Цікаво. Не могли б ви опублікувати якийсь код чи, можливо, уникнути того, що сидить у вашій глибині нижче, що робить це?
Пітер Ліллевольд,

Я не можу сказати вам, чи це помилка CLR ... важко відстежити, що в кишках .NET кинуло null і чому, якщо ви не отримаєте виняток зі стеком викликів або будь-якими іншими підказками, про які можна було б сказати далі. Я просто знаю, що мій зовнішній улов тепер перевіряє наявність аргументу null Exception перед його використанням.
Брайан Кеннеді

3
Я підозрюю, що це помилка CLR або через поганий неперевіряний код. Правильний CIL викине лише ненульовий об'єкт або нульовий (який стає NullReferenceException).
Демі

Я також використовую службу, яка повертає нульове виняток. Ви коли-небудь з'ясовували, як створюється нульовий виняток?
themiDdlest

2

Я думаю, можливо, ви не можете - коли ви намагаєтесь кинути null, це не може, тому він робить те, що має, у випадку помилки, тобто викидання нульового виключення посилання. Отже, ви насправді не кидаєте нуль, вам не вдається кинути нуль, що призводить до кидка.


2

Намагання відповісти "..з вдячністю" колишній "не є нульовим, але чи може це бути коли-небудь?":

Оскільки ми, безперечно, не можемо викидати винятки, які є нульовими, речення catch також ніколи не доведеться ловити виняток, який є нульовим. Таким чином, ex ніколи не може бути нулем.

Зараз я бачу, що це питання насправді вже було задано .


@Brian Kennedy говорить нам у своєму коментарі вище, що ми можемо зловити нуль.
ProfK

@ Tvde1 - Думаю, тут відмінність полягає в тому, що так, ви можете виконати throw null. Але це не призведе до винятку, який є нульовим . Швидше, оскільки throw nullнамагається викликати методи з нульовим посиланням, це згодом змушує час виконання кидати ненульовий екземпляр NullReferenceException.
Пітер Ліллевольд,

1

Пам’ятайте, що виняток включає деталі щодо того, куди кинуто виняток. Вбачаючи, що конструктор не має уявлення, куди його потрібно кинути, тоді має сенс лише те, що метод throw вводить ці деталі в об'єкт у точці, в якій знаходиться метання. Іншими словами, CLR намагається ввести дані в null, що викликає NullReferenceException.

Не впевнений, що це саме те, що відбувається, але це пояснює явище.

Якщо припустити, що це правда (і я не можу придумати кращого способу отримати ex як null, ніж throw null;), це означало б, що ex не може бути null.


0

У старішому c #:

Розглянемо цей синтаксис:

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 no 1-го вбудовано, тож він точно коротший. Те, що я намагався сказати, - це те, що ти можеш видавати помилку залежно від стану. Традиційно u хотів би робити 2-й приклад, але оскільки "throw null" нічого не кидає, u може вставити другий приклад, щоб виглядати як 1-й приклад. Також 1-й приклад має на 8 символів менше 2-го
Арутюн Енфенджян

“Throw null” видає виняток NullReference. Це означає, що ваш перший приклад завжди буде винятком.
bornfromanegg

так, на жаль, це відбувається зараз. Але раніше цього не сталося
Арутюн Енфенджян
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.