Чи є різниця між "кинути" та "кинути колишнє"?


437

Є кілька публікацій, які запитують, яка різниця між цими двома вже є.
(чому я маю це навіть згадувати ...)

Але моє запитання відрізняється тим, що я закликаю "кинути екс" в іншому богоподібному способі обробки.

public class Program {
    public static void Main(string[] args) {
        try {
            // something
        } catch (Exception ex) {
            HandleException(ex);
        }
    }

    private static void HandleException(Exception ex) {
        if (ex is ThreadAbortException) {
            // ignore then,
            return;
        }
        if (ex is ArgumentOutOfRangeException) { 
            // Log then,
            throw ex;
        }
        if (ex is InvalidOperationException) {
            // Show message then,
            throw ex;
        }
        // and so on.
    }
}

Якби try & catchвони використовувалися в Main, тоді я б використовував throw;для повторного усунення помилки. Але у наведеному вище спрощеному коді всі винятки проходять черезHandleException

Чи throw ex;має такий же ефект, як дзвінок, throwколи дзвонили всередину HandleException?


3
Існує різниця, вона пов'язана з тим, чи відображається стеження стека у винятку, але я не пам’ятаю, що саме зараз, тому я не перелічу цю відповідь.
Joel Coehoorn

@Joel: Дякую Я думаю, що виключення HandleError - це погана ідея. Я просто хотів відновити деякий код обробки помилок.
dance2die

1
Третій спосіб - перетворитись на новий виняток і перекинути timwise.blogspot.co.uk/2014/05/…
Тім

Відповіді:


679

Так, є різниця;

  • throw exскидає слід стека (так, здається, ваші помилки походять від HandleException)
  • throw ні - оригінальний злочинець буде збережений.

    static void Main(string[] args)
    {
        try
        {
            Method2();
        }
        catch (Exception ex)
        {
            Console.Write(ex.StackTrace.ToString());
            Console.ReadKey();
        }
    }
    
    private static void Method2()
    {
        try
        {
            Method1();
        }
        catch (Exception ex)
        {
            //throw ex resets the stack trace Coming from Method 1 and propogates it to the caller(Main)
            throw ex;
        }
    }
    
    private static void Method1()
    {
        try
        {
            throw new Exception("Inside Method1");
        }
        catch (Exception)
        {
            throw;
        }
    }
    

28
Щоб трохи розширити відповідь Марка, ви можете дізнатися більше деталей тут: geekswithblogs.net/sdorman/archive/2007/08/20/…
Скотт Дорман

3
@Shaul; ні, це не так. Я розповів деталі в коментарі до вашого повідомлення.
Марк Гравелл

1
@Marc Gravell - мої вибачення, ви мали рацію. Вибачте за потік; мені вже пізно скасовувати ... :(
Шауль Бехр

3
@Marc: Здається, що кидок зберігає ТОЛЬКО первинного правопорушника, якщо кидок не є методом, у якому було кинуто первинний виняток (див. Це питання: stackoverflow.com/questions/5152265/… )
Brann

3
@ScottDorman Схоже, ваше посилання не передається правильно після міграції блогу. Схоже, це зараз живе тут . Редагувати: Гей, чекай, ось ваш блог! Виправити власні посилання! ; ^ D
ruffin

96

(Я публікував раніше, і @Marc Gravell мене виправив)

Ось демонстрація різниці:

static void Main(string[] args) {
    try {
        ThrowException1(); // line 19
    } catch (Exception x) {
        Console.WriteLine("Exception 1:");
        Console.WriteLine(x.StackTrace);
    }
    try {
        ThrowException2(); // line 25
    } catch (Exception x) {
        Console.WriteLine("Exception 2:");
        Console.WriteLine(x.StackTrace);
    }
}

private static void ThrowException1() {
    try {
        DivByZero(); // line 34
    } catch {
        throw; // line 36
    }
}
private static void ThrowException2() {
    try {
        DivByZero(); // line 41
    } catch (Exception ex) {
        throw ex; // line 43
    }
}

private static void DivByZero() {
    int x = 0;
    int y = 1 / x; // line 49
}

і ось вихід:

Exception 1:
   at UnitTester.Program.DivByZero() in <snip>\Dev\UnitTester\Program.cs:line 49
   at UnitTester.Program.ThrowException1() in <snip>\Dev\UnitTester\Program.cs:line 36
   at UnitTester.Program.TestExceptions() in <snip>\Dev\UnitTester\Program.cs:line 19

Exception 2:
   at UnitTester.Program.ThrowException2() in <snip>\Dev\UnitTester\Program.cs:line 43
   at UnitTester.Program.TestExceptions() in <snip>\Dev\UnitTester\Program.cs:line 25

Ви можете бачити, що у винятку 1 слід стека повертається до DivByZero()методу, тоді як у винятку 2 - ні.

Прийміть до відома, однак, що номер рядка показаний ThrowException1()і ThrowException2()є номер рядка throwзаяви, НЕ номер рядка виклику DivByZero(), який , ймовірно , має сенс тепер, коли я думаю про це трохи ...

Вихід у режимі випуску

Виняток 1:

at ConsoleAppBasics.Program.ThrowException1()
at ConsoleAppBasics.Program.Main(String[] args)

Виняток 2:

at ConsoleAppBasics.Program.ThrowException2()
at ConsoleAppBasics.Program.Main(String[] args)

Чи підтримує оригінальний stackTrace лише в режимі налагодження?


1
Це пояснюється тим, що процес оптимізації компілятора вказує на короткі методи, такі як DevideByZero, тому слід стека однаковий. можливо, ви повинні залишити це питання самостійно
Менахем

42

Інші відповіді цілком правильні, але ця відповідь дає деякі додаткові відомості.

Розглянемо цей приклад:

using System;

static class Program {
  static void Main() {
    try {
      ThrowTest();
    } catch (Exception e) {
      Console.WriteLine("Your stack trace:");
      Console.WriteLine(e.StackTrace);
      Console.WriteLine();
      if (e.InnerException == null) {
        Console.WriteLine("No inner exception.");
      } else {
        Console.WriteLine("Stack trace of your inner exception:");
        Console.WriteLine(e.InnerException.StackTrace);
      }
    }
  }

  static void ThrowTest() {
    decimal a = 1m;
    decimal b = 0m;
    try {
      Mult(a, b);  // line 34
      Div(a, b);   // line 35
      Mult(b, a);  // line 36
      Div(b, a);   // line 37
    } catch (ArithmeticException arithExc) {
      Console.WriteLine("Handling a {0}.", arithExc.GetType().Name);

      //   uncomment EITHER
      //throw arithExc;
      //   OR
      //throw;
      //   OR
      //throw new Exception("We handled and wrapped your exception", arithExc);
    }
  }

  static void Mult(decimal x, decimal y) {
    decimal.Multiply(x, y);
  }
  static void Div(decimal x, decimal y) {
    decimal.Divide(x, y);
  }
}

Якщо ви коментуєте throw arithExc;рядок, ваш результат:

Handling a DivideByZeroException.
Your stack trace:
   at Program.ThrowTest() in c:\somepath\Program.cs:line 44
   at Program.Main() in c:\somepath\Program.cs:line 9

No inner exception.

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

Handling a DivideByZeroException.
Your stack trace:
   at System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)
   at System.Decimal.Divide(Decimal d1, Decimal d2)
   at Program.Div(Decimal x, Decimal y) in c:\somepath\Program.cs:line 58
   at Program.ThrowTest() in c:\somepath\Program.cs:line 46
   at Program.Main() in c:\somepath\Program.cs:line 9

No inner exception.

Це набагато краще, тому що зараз ви бачите, що саме Program.Divметод викликав у вас проблеми. Але все ще важко зрозуміти, чи виникає ця проблема з лінії 35 або лінії 37 в tryблоці.

Якщо ви використовуєте третю альтернативу, завернувши зовнішній виняток, ви втрачаєте інформацію:

Handling a DivideByZeroException.
Your stack trace:
   at Program.ThrowTest() in c:\somepath\Program.cs:line 48
   at Program.Main() in c:\somepath\Program.cs:line 9

Stack trace of your inner exception:
   at System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)
   at System.Decimal.Divide(Decimal d1, Decimal d2)
   at Program.Div(Decimal x, Decimal y) in c:\somepath\Program.cs:line 58
   at Program.ThrowTest() in c:\somepath\Program.cs:line 35

Зокрема, ви бачите, що саме ця проблема призводить до лінії 35 . Однак для цього потрібні люди шукати InnerException, і внутрішні винятки у простих випадках відчувають дещо опосередкованим.

У цій публікації щоденника вони зберігають номер рядка (рядок блоку спробу), викликаючи (через відображення) internalметод примірника InternalPreserveStackTrace()на Exceptionоб’єкт. Але недобре використовувати таке відображення (.NET Framework може змінити своїх internalчленів якось без попередження).


6

давайте зрозуміємо різницю між кидком та кидком екс. Я чув, що в багатьох інтерв'ю .net про це запитують.

Просто для ознайомлення з цими двома термінами, кидок і кидок екс використовуються, щоб зрозуміти, де стався виняток. Throw ex переписує слід виключення стека незалежно від того, куди насправді було кинуто.

Розберемося на прикладі.

Давайте розберемося спочатку кинь.

static void Main(string[] args) {
    try {
        M1();
    } catch (Exception ex) {
        Console.WriteLine(" -----------------Stack Trace Hierarchy -----------------");
        Console.WriteLine(ex.StackTrace.ToString());
        Console.WriteLine(" ---------------- Method Name / Target Site -------------- ");
        Console.WriteLine(ex.TargetSite.ToString());
    }
    Console.ReadKey();
}

static void M1() {
    try {
        M2();
    } catch (Exception ex) {
        throw;
    };
}

static void M2() {
    throw new DivideByZeroException();
}

висновок вищезазначеного нижче.

показує повну ієрархію та ім'я методу, де насправді виняток викинув .. це M2 -> M2. разом з номерами рядків

введіть тут опис зображення

По-друге .. давайте зрозуміти, кинувши екс. Просто замініть кидок на викид у блоці методу M2. як зазначено нижче.

введіть тут опис зображення

Вихід ex код код як нижче ..

введіть тут опис зображення

Ви можете побачити різницю у виведенні. Кидок ex просто ігнорує всю попередню ієрархію та скидає трасування стека за допомогою рядка / методу, де написано кидання екс.


5

Коли ви це зробите throw ex, цей викинутий виняток стає "оригінальним". Тож усіх попередніх стеків не буде.

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


4

Ні, це призведе до того, що виняток має інший слід стека. Тільки використання throwоб'єкта без будь-якого винятку в catchобробнику залишить слід стека незмінним.

Ви можете повернути булеве значення з HandleException, буде виняток перезаписано чи ні.


4

MSDN означає :

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


2

Подивіться тут: http://blog-mstechnology.blogspot.de/2010/06/throw-vs-throw-ex.html

Кинути :

try 
{
    // do some operation that can fail
}
catch (Exception ex)
{
    // do some local cleanup
    throw;
}

Він зберігає інформацію про стек за винятком

Це називається "Знову"

Якщо ви хочете викинути новий виняток,

throw new ApplicationException("operation failed!");

Кинути екс :

try
{
    // do some operation that can fail
}
catch (Exception ex)
{
    // do some local cleanup
    throw ex;
}

Він не надсилатиме інформацію про стек за винятком

Це називається "Розбиття стека"

Якщо ви хочете викинути новий виняток,

throw new ApplicationException("operation failed!",ex);

0

Щоб дати вам іншу точку зору, використання функцій кидання є особливо корисним, якщо ви надаєте API клієнту та хочете надати інформацію про стеження багатослівного стека для вашої внутрішньої бібліотеки. Використовуючи кинути тут, я отримав би слід стека в цьому випадку бібліотеки System.IO.File для File.Delete. Якщо я використовую кидок ex, то ця інформація не буде передана моєму оброблювачу.

static void Main(string[] args) {            
   Method1();            
}

static void Method1() {
    try {
        Method2();
    } catch (Exception ex) {
        Console.WriteLine("Exception in Method1");             
    }
}

static void Method2() {
    try {
        Method3();
    } catch (Exception ex) {
        Console.WriteLine("Exception in Method2");
        Console.WriteLine(ex.TargetSite);
        Console.WriteLine(ex.StackTrace);
        Console.WriteLine(ex.GetType().ToString());
    }
}

static void Method3() {
    Method4();
}

static void Method4() {
    try {
        System.IO.File.Delete("");
    } catch (Exception ex) {
        // Displays entire stack trace into the .NET 
        // or custom library to Method2() where exception handled
        // If you want to be able to get the most verbose stack trace
        // into the internals of the library you're calling
        throw;                
        // throw ex;
        // Display the stack trace from Method4() to Method2() where exception handled
    }
}

-1
int a = 0;
try {
    int x = 4;
    int y ;
    try {
        y = x / a;
    } catch (Exception e) {
        Console.WriteLine("inner ex");
        //throw;   // Line 1
        //throw e;   // Line 2
        //throw new Exception("devide by 0");  // Line 3
    }
} catch (Exception ex) {
    Console.WriteLine(ex);
    throw ex;
}
  1. якщо всі рядки 1, 2 і 3 коментовані - вихідний - внутрішній колишній

  2. якщо всі 2 і 3 коментуються - Вихід - внутрішній колишній System.DevideByZeroException: {"Спроба розділити на нуль."} ---------

  3. якщо коментуються всі рядки 1 та 2 - Вихід - внутрішній колишній System.Exception: розділити на 0 ----

  4. якщо коментуються всі рядки 1 та 3 - Вихід - внутрішній колишній System.DevideByZeroException: {"Спроба розділити на нуль."} ---------

а StackTrace буде скинутий у разі викидання екс;

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