різниця між кидком та кидком нового винятку ()


164

яка різниця між

try { ... }
catch{ throw } 

і

try{ ... }
catch(Exception e) {throw new Exception(e.message) } 

Незалежно від того, що другий показує повідомлення?


51
Другий фрагмент - одна з найбільш злих (але нешкідливих) ліній коду, яку я коли-небудь бачив.
СЛАкс

Відповіді:


259

throw; перезавантажує початковий виняток і зберігає його початковий слід стека.

throw ex;кидає оригінальний виняток, але скидає слід стека, знищуючи всю інформацію про сліди стека до вашого catchблоку.


НІКОЛИ не пишітьthrow ex;


throw new Exception(ex.Message);ще гірше. Це створює абсолютно новий Exceptionекземпляр, втрачаючи початковий слід стека виключення, а також його тип. (наприклад, IOException).
Крім того, деякі винятки містять додаткову інформацію (наприклад, ArgumentException.ParamName).
throw new Exception(ex.Message); знищить і цю інформацію.

У деяких випадках ви можете зафіксувати всі винятки в спеціальному об’єкті виключення, щоб ви могли надати додаткову інформацію про те, що робив код, коли викид було викинуто.

Для цього визначте новий клас, який успадковується Exception, додайте всі чотири конструктори винятків та, необов'язково, додатковий конструктор, який приймає InnerExceptionяк додаткову інформацію, так і викиньте ваш новий клас виключень, передаючи exяк InnerExceptionпараметр . Передаючи оригінал InnerException, ви зберігаєте всі властивості оригінального винятку, включаючи слід стека.


24
"кинути новий виняток (колишній); ще гірше.": Я не згоден з цим. Іноді ви хочете змінити тип винятку, і тоді найкраще ви можете зберегти початковий виняток як внутрішній виняток. Хоча це повинно бути, throw new MyCustomException(myMessage, ex);звичайно.
Дірк Волмар

9
@ 0xA3: Я мав в виду ex.Message, що це гірше.
СЛАкс

6
Окрім впровадження стандартних конструкторів, слід також робити власні винятки [Serializable()].
Дірк Волмар

21
Yo dawg, ми стадо любите винятки, тому ми ставимо виняток у yo 'виняток, щоб ви могли зловити, поки ви ловите.
Дарт Континент

2
@SLaks: коли ви throw;фактичний номер рядка, де стався виняток, замінюється номером рядка throw;. Як ви пропонуєте вирішити це? stackoverflow.com/questions/2493779/…
Ерік Дж.

34

Перший зберігає оригінальний стек-трек:

try { ... }
catch
{
    // Do something.
    throw;
}

Друга дозволяє змінити тип виключення та / або повідомлення та інші дані:

try { ... } catch (Exception e)
{
    throw new BarException("Something broke!");
}

Існує також третій спосіб, коли ви передаєте внутрішній виняток:

try { ... }
catch (FooException e) {
    throw new BarException("foo", e);
} 

Я рекомендую використовувати:

  • перше, якщо ви хочете зробити чистку в ситуації з помилками, не знищуючи інформацію або додаючи інформацію про помилку.
  • третє, якщо ви хочете додати більше інформації про помилку.
  • друге, якщо ви хочете приховати інформацію (від недовірених користувачів).

6

Ще один момент, який я не бачив, щоб хтось робив:

Якщо ви нічого не зробите у своєму блоці {{}, спробувати ... ловити безглуздо. Я це бачу постійно:

try 
{
  //Code here
}
catch
{
    throw;
}

Або ще гірше:

try 
{
  //Code here
}
catch(Exception ex)
{
    throw ex;
}

Найгірше:

try 
{
  //Code here
}
catch(Exception ex)
{
    throw new System.Exception(ex.Message);
}

Я згоден, якщо ви не маєте остаточного пункту.
Тоні Россманн

1
@ToniRossmann У цьому випадку я б використав спробу .. остаточно без улову, якщо тільки ви не робите щось, крім кидка;
JLWarlow

4

throwповторно викидає спійманий виняток, зберігаючи слід стека, при цьому throw new Exceptionвтрачає деякі деталі спійманого винятку.

Зазвичай ви самі використовуєте throwдля реєстрації винятку, не повністю обробляючи його в той момент.

BlackWasp має хорошу статтю, достатньо під назвою Викидання викидів у C # .


4

Кидання нового винятку видаляє поточний слід стека.

throw;збереже початковий слід стека і майже завжди є більш корисним. Виняток із цього правила полягає в тому, коли ви хочете обгорнути Виняток у власному власному Винятку. Потім слід зробити:

catch(Exception e)
{
    throw new CustomException(customMessage, e);
}

3

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

Використання throwбез жодних аргументів зберігає стек викликів для налагодження.


0

Якщо ви хочете, ви можете кинути новий виняток, при цьому оригінальний набір є внутрішнім винятком.


0

Ваш другий приклад скидає слід стека винятку. Перший найбільш точно зберігає витоки винятку. Крім того, ви розгорнули оригінальний тип, який є ключовим у розумінні того, що насправді пішло не так ... Якщо для функціональності потрібен другий - наприклад, для додавання розширеної інформації або повторного загортання зі спеціальним типом, таким як користувацький 'HandleableException', тоді просто будьте переконайтеся, що також встановлено властивість InnerException!


Так, це одне з тих питань, де потрібно швидко писати. ;)
Роберт Харві

0

Найголовніша відмінність полягає в тому, що другий вираз стирає тип винятку. І тип винятку відіграє життєво важливу роль у вилученні винятків:

public void MyMethod ()
{
    // both can throw IOException
    try { foo(); } catch { throw; }
    try { bar(); } catch(E) {throw new Exception(E.message); }
}

(...)

try {
    MyMethod ();
} catch (IOException ex) {
    Console.WriteLine ("Error with I/O"); // [1]
} catch (Exception ex) {
    Console.WriteLine ("Other error");    // [2]
}

Якщо foo()кидки IOException, [1]блок лову виловить виключення. Але коли bar()кидки IOException, він буде перетворений на звичайного Exceptionмураха, не буде спійманий [1]ловом блоку.


0

кинути або кинути колишнє, обидва використовуються для викидання або повторного скидання винятку, коли ви просто записуєте інформацію про помилку і не хочете надсилати будь-яку інформацію назад абоненту, ви просто заносите помилку в лову та залишення. Але у випадку, якщо ви хочете надіслати якусь змістовну інформацію про виняток абоненту, який ви використовуєте кинути або кинути колишню. Тепер різниця між екс-кидком і кидком полягає в тому, що кид зберігає слід стека та іншу інформацію, але кидок "ex" створює новий об'єкт виключення, і, отже, початковий слід стека втрачається. Отже, коли нам слід використовувати кидок і кидання e, Є ще кілька ситуацій, у яких ви, можливо, захочете повторно скинути виняток, як-от скинути інформацію про стек виклику. Наприклад, якщо метод знаходиться в бібліотеці, і ви хочете заховати дані про бібліотеку з викликового коду, не обов'язково потрібно, щоб стек викликів містив інформацію про приватні методи в бібліотеці. У цьому випадку ви можете зафіксувати винятки в публічних методах бібліотеки, а потім повторно скинути їх, щоб стек викликів починався з цих публічних методів.


0

Кинути; Перезавантажте початковий виняток і збережіть тип виключення.

Киньте новий виняток (); Перезавантажте початковий тип винятку та скиньте слід стека винятку

Кинути екс; Скиньте слід стека винятку та скиньте тип винятку


-1

Жодна з відповідей тут не показує різниці, яка може бути корисною для людей, які намагаються зрозуміти різницю. Розглянемо цей зразок коду:

using System;
using System.Collections.Generic;

namespace ExceptionDemo
{
   class Program
   {
      static void Main(string[] args)
      {
         void fail()
         {
            (null as string).Trim();
         }

         void bareThrow()
         {
            try
            {
               fail();
            }
            catch (Exception e)
            {
               throw;
            }
         }

         void rethrow()
         {
            try
            {
               fail();
            }
            catch (Exception e)
            {
               throw e;
            }
         }

         void innerThrow()
         {
            try
            {
               fail();
            }
            catch (Exception e)
            {
               throw new Exception("outer", e);
            }
         }

         var cases = new Dictionary<string, Action>()
         {
            { "Bare Throw:", bareThrow },
            { "Rethrow", rethrow },
            { "Inner Throw", innerThrow }
         };

         foreach (var c in cases)
         {
            Console.WriteLine(c.Key);
            Console.WriteLine(new string('-', 40));
            try
            {
               c.Value();
            } catch (Exception e)
            {
               Console.WriteLine(e.ToString());
            }
         }
      }
   }
}

Що генерує такий вихід:

Bare Throw:
----------------------------------------
System.NullReferenceException: Object reference not set to an instance of an object.
   at ExceptionDemo.Program.<Main>g__fail|0_0() in C:\...\ExceptionDemo\Program.cs:line 12
   at ExceptionDemo.Program.<>c.<Main>g__bareThrow|0_1() in C:\...\ExceptionDemo\Program.cs:line 19
   at ExceptionDemo.Program.Main(String[] args) in C:\...\ExceptionDemo\Program.cs:line 64

Rethrow
----------------------------------------
System.NullReferenceException: Object reference not set to an instance of an object.
   at ExceptionDemo.Program.<>c.<Main>g__rethrow|0_2() in C:\...\ExceptionDemo\Program.cs:line 35
   at ExceptionDemo.Program.Main(String[] args) in C:\...\ExceptionDemo\Program.cs:line 64

Inner Throw
----------------------------------------
System.Exception: outer ---> System.NullReferenceException: Object reference not set to an instance of an object.
   at ExceptionDemo.Program.<Main>g__fail|0_0() in C:\...\ExceptionDemo\Program.cs:line 12
   at ExceptionDemo.Program.<>c.<Main>g__innerThrow|0_3() in C:\...\ExceptionDemo\Program.cs:line 43
   --- End of inner exception stack trace ---
   at ExceptionDemo.Program.<>c.<Main>g__innerThrow|0_3() in C:\...\ExceptionDemo\Program.cs:line 47
   at ExceptionDemo.Program.Main(String[] args) in C:\...\ExceptionDemo\Program.cs:line 64

Голий кидок, як зазначено в попередніх відповідях, чітко показує як вихідний рядок коду, який не вдався (рядок 12), так і два інші пункти, активні в стеку викликів, коли сталося виключення (рядки 19 та 64).

Вихід із випадку повторного кидання показує, чому це проблема. Коли виняток повторно скидається, цей виняток не буде містити вихідну інформацію про стек. Зауважте, що throw eвключені лише (лінія 35) та найзадаліша точка стека викликів (лінія 64). Було б важко відстежити метод fail () як джерело проблеми, якщо ви викинете винятки таким чином.

Останній випадок (innerThrow) є найбільш детальним і включає більше інформації, ніж будь-який із наведених. Оскільки ми створюємо новий виняток, ми отримуємо можливість додати контекстну інформацію ("зовнішнє" повідомлення, тут, але ми можемо також додати до словника .Data за новим винятком), а також зберегти всю інформацію в оригіналі виняток (включаючи довідкові посилання, словник даних тощо).

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