Найкращі практики вилучення та повторного викидання .NET винятків


284

Які найкращі практики слід враховувати під час вилучення винятків та їх повторного скидання? Я хочу переконатися, що слід і Exceptionоб'єкт InnerExceptionстека збереглися. Чи є різниця між наступними блоками коду в тому, як вони справляються з цим?

try
{
    //some code
}
catch (Exception ex)
{
    throw ex;
}

Vs:

try
{
    //some code
}
catch
{
    throw;
}

Відповіді:


262

Спосіб зберегти трасування стека через використання throw;Це дійсно так

try {
  // something that bombs here
} catch (Exception ex)
{
    throw;
}

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

Майк також правильний, якщо припустити, що виняток дозволяє передавати виняток (що рекомендується).

Карл Сегуїн також чудово пише про обробку винятків у своїх основах програмування електронної книги , що є чудовим читанням.

Редагувати: Робоче посилання на Основи програмування pdf. Просто шукайте текст "виняток".


10
Я не дуже впевнений, що це записування чудово, я пропоную спробувати {// ...} catch (Exception ex) {кинути новий виняток (ex.Message + "інші речі"); } це добре. Проблема полягає в тому, що ви повністю не в змозі обробляти цей виняток в будь-якій подальшій версії стека, якщо ви не знайдете всі винятки, великий ні-ні (ви впевнені, що хочете обробити цей OutOfMemoryException?)
— ljs,

2
@ljs Чи змінилася стаття після вашого коментаря, оскільки я не бачу жодного розділу, де він рекомендує це. Насправді навпаки, він каже, що не робити цього, і запитує, чи хочете ви також обробляти OutOfMemoryException !?
— RyanfaeScotland

6
Іноді кидають; недостатньо для збереження сліду стека. Ось приклад https://dotnetfiddle.net/CkMFoX
— Артавазд Балаян

4
Або ExceptionDispatchInfo.Capture(ex).Throw(); throw;в .NET +4,5 stackoverflow.com/questions/57383 / ...
— Альфред Уоллес

Рішення @AlfredWallace для мене прекрасно працювало. спробуйте {...} catch {кидок} не зберег слід сліду. Дякую.
— atownson

100

Якщо ви кинете новий виняток із початковим винятком, ви збережете і початковий слід стека.

try{
} 
catch(Exception ex){
     throw new MoreDescriptiveException("here is what was happening", ex);
}

Незалежно від того, що я намагаюся кинути новий виняток ("повідомлення", колишній), завжди викидає колишній і ігнорує користувацьке повідомлення. кинути новий виняток ("повідомлення", ex.InnerException) працює, хоча.
— Тод

Якщо не потрібен спеціальний виняток, можна використовувати AggregateException (.NET 4+) msdn.microsoft.com/en-us/library/…
— Нікос Цокос

AggregateExceptionслід використовувати лише для виключень із сукупних операцій. Наприклад, це викидається класами CLR ParallelEnumerableта Taskкласами. Використання, ймовірно, слід за цим прикладом.
— Алуан Хаддад

29

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

try
{
  int i = 0;
  int j = 12 / i; // Line 47
  int k = j + 1;
}
catch
{
  // do something
  // ...
  throw; // Line 54
}

StackTrace вкаже, що рядок 54 підвищив виняток, хоча він був піднятий у рядку 47.

Unhandled Exception: System.DivideByZeroException: Attempted to divide by zero.
   at Program.WithThrowIncomplete() in Program.cs:line 54
   at Program.Main(String[] args) in Program.cs:line 106

У таких ситуаціях, як описана вище, є два варіанти попереднього встановлення початкового StackTrace:

Виклик винятку.InternalPreserveStackTrace

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

private static void PreserveStackTrace(Exception exception)
{
  MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace",
    BindingFlags.Instance | BindingFlags.NonPublic);
  preserveStackTrace.Invoke(exception, null);
}

У мене є недоліком покладатися на приватний метод збереження інформації StackTrace. Його можна змінити в майбутніх версіях .NET Framework. Наведений вище приклад коду та запропоноване рішення нижче були вилучені з веб-журналу Fabrice MARGUERIE .

Виклик винятку.SetObjectData

Наведена нижче методика запропонувала Антон Тихий як відповідь на In C #, як я можу переосмислити InnerException, не втрачаючи питання сліду стека .

static void PreserveStackTrace (Exception e) 
{ 
  var ctx = new StreamingContext  (StreamingContextStates.CrossAppDomain) ; 
  var mgr = new ObjectManager     (null, ctx) ; 
  var si  = new SerializationInfo (e.GetType (), new FormatterConverter ()) ; 

  e.GetObjectData    (si, ctx)  ; 
  mgr.RegisterObject (e, 1, si) ; // prepare for SetObjectData 
  mgr.DoFixups       ()         ; // ObjectManager calls SetObjectData 

  // voila, e is unmodified save for _remoteStackTraceString 
} 

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

protected Exception(
    SerializationInfo info,
    StreamingContext context
)

У моїй ситуації мені довелося обрати перший підхід, оскільки винятки, створені сторонньою бібліотекою, яку я використовував, не реалізували цей конструктор.


1
Ви можете зловити виняток і опублікувати цей виняток у будь-якому місці. Потім киньте нове, пояснюючи, що сталося з користувачем. Таким чином, ви можете побачити, що сталося в поточний час, коли виняток був спійманий, користувач може небайдуже, що було фактичним винятком.
— Çöđěxěŕ

2
З .NET 4.5 є третій і - на мій погляд - більш чистий варіант: використовуйте ExceptionDispatchInfo. Дивіться відповіді трагедій на відповідне запитання тут: stackoverflow.com/a/17091351/567000 для отримання додаткової інформації.
— Søren Boisen

20

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


13

Головне правило - уникати лову та метання основного Exceptionоб'єкта. Це змушує вас бути трохи розумнішими щодо винятків; іншими словами, у вас повинен бути чіткий улов для SqlExceptionтого, щоб ваш код обробки не робив щось не так з a NullReferenceException.

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


2
Я думаю, що найкраще боротися з необробленими винятками для цілей реєстрації, використовуючи винятки AppDomain.CurrentDomain.UnhandledException та Application.ThreadException. Використання великих блоків {...} catch (Exception ex) {...} скрізь означає багато дублювання. Залежить від того, чи хочете ви реєструвати оброблювані винятки, і в цьому випадку (принаймні мінімальне) дублювання може бути неминучим.
— ljs

Крім того, використовуючи ці події означають , що ви робите реєструвати всі необроблені виключення, в той час як якщо ви використовуєте великий Ол »спробувати {...} спіймати (Exception ех) {...} блоки ви можете пропустити деякі з них .
— ljs

10

Ви завжди повинні використовувати "кинути"; повторно скинути винятки в .NET,

Посилайтеся на це, http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx

В основному MSIL (CIL) має дві інструкції - "кинути" і "перекинути":

  • C # 's "кинути екс;" збирається в "кидок" MSIL
  • C # 's "кинути;" - в MSIL "переробити"!

В основному я бачу причину, чому "кинути екс" переосмислює слід стека.


Посилання - ну, власне, джерело, на яке посилається посилання, - переповнене хорошою інформацією, а також зазначає можливого винуватця того, чому багато хто думає throw ex;, що повторно впаде - на Java це і є! Але ви повинні включити цю інформацію сюди, щоб мати відповідь класу. (Хоча я все ще наздоганяю ExceptionDispatchInfo.Captureвідповідь від jeuoekdcwzfwccu .)
— ruffin

10

Ніхто не пояснив різницю між ExceptionDispatchInfo.Capture( ex ).Throw()та звичайною throw, так ось вона. Однак деякі люди помітили проблему throw.

Повний спосіб повторного вигнання спійманого винятку - це використання ExceptionDispatchInfo.Capture( ex ).Throw()(доступне лише у .Net 4.5).

Нижче наведені випадки, необхідні для перевірки цього:

1.

void CallingMethod()
{
    //try
    {
        throw new Exception( "TEST" );
    }
    //catch
    {
    //    throw;
    }
}

2.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch( Exception ex )
    {
        ExceptionDispatchInfo.Capture( ex ).Throw();
        throw; // So the compiler doesn't complain about methods which don't either return or throw.
    }
}

3.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch
    {
        throw;
    }
}

4.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch( Exception ex )
    {
        throw new Exception( "RETHROW", ex );
    }
}

Випадок 1 і випадок 2 дадуть вам стеження за стеком, де номер рядка вихідного коду для CallingMethodметоду є номером throw new Exception( "TEST" )рядка рядка.

Однак випадок 3 надасть вам стеження стека, де номер рядка вихідного коду для CallingMethodметоду є номером рядка throwвиклику. Це означає, що якщо throw new Exception( "TEST" )рядок оточений іншими операціями, ви не маєте уявлення, на якому номері рядка було виключено виняток.

Випадок 4 аналогічний випадку 2, оскільки номер рядка вихідного винятку зберігається, але не є реальним перезарядкою, оскільки він змінює тип вихідного винятку.


Додайте просту розмитості ніколи не використовувати, throw ex;і це найкраща відповідь на них усіх.
— NH.

8

Кілька людей насправді пропустили дуже важливий момент - «кинути» та «кинути екс» можуть зробити те саме, але вони не дають тобі важливої ​​інформації, яка є лінією, де сталося виняток.

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

static void Main(string[] args)
{
    try
    {
        TestMe();
    }
    catch (Exception ex)
    {
        string ss = ex.ToString();
    }
}

static void TestMe()
{
    try
    {
        //here's some code that will generate an exception - line #17
    }
    catch (Exception ex)
    {
        //throw new ApplicationException(ex.ToString());
        throw ex; // line# 22
    }
}

Коли ви робите або "кинути", або "кинути колишнє", ви отримуєте слід стека, але рядок №2 буде # 22, тому ви не можете зрозуміти, який рядок точно кидав виняток (якщо у вас є лише 1 або кілька рядки коду в блоці спробу). Щоб отримати очікуваний рядок №17 у своєму винятку, вам доведеться викинути новий виняток з оригінальним слідом стека винятків.


3

Ви також можете використовувати:

try
{
// Dangerous code
}
finally
{
// clean up, or do nothing
}

І будь-які викиди, що викидаються, піднятимуться до наступного рівня, який обробляє їх.


3

Я б точно використав:

try
{
    //some code
}
catch
{
    //you should totally do something here, but feel free to rethrow
    //if you need to send the exception up the stack.
    throw;
}

Це збереже ваш стек.


1
Справедливості до минулого в 2008 році, ОП запитувала, як зберегти стек - і в 2008 році я дав правильну відповідь. Чого не вистачає в моїй відповіді, це частина того, що насправді щось робити в улові.
— 1kevgriff

@JohnSaunders Це правда, якщо і тільки якщо ви нічого не робите раніше throw; наприклад, ви можете очистити одноразові (де ви ТИЛЬКО називаєте це помилкою), а потім кинути виняток.
— Meirion Hughes

@meirion, коли я писав коментар, перед кидком нічого не було. Коли це було додано, я підтримав, але коментар не видалив.
— Джон Сондерс

0

FYI Я щойно перевірив це і слід стека, про який повідомляється "кинути"; не є абсолютно правильним слідом стека. Приклад:

    private void foo()
    {
        try
        {
            bar(3);
            bar(2);
            bar(1);
            bar(0);
        }
        catch(DivideByZeroException)
        {
            //log message and rethrow...
            throw;
        }
    }

    private void bar(int b)
    {
        int a = 1;
        int c = a/b;  // Generate divide by zero exception.
    }

Шлях стеження правильно вказує на початок винятку (повідомлений номер рядка), але номер рядка, повідомлений для foo (), є рядком кидка; заява, отже, ви не можете сказати, який із дзвінків на бар () викликав виняток.


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