Kiedy w końcu zostanie uruchomiony, jeśli wyrzucisz wyjątek z bloku catch?


141
try {
   // Do stuff
}
catch (Exception e) {
   throw;
}
finally {
   // Clean up
}

Kiedy w powyższym bloku wywoływany jest ostatni blok? Przed rzuceniem e, czy w końcu zostaje sprawdzony, a następnie złapany?


16
ps nie powinieneś "rzucać e"; ponieważ to zepsuje ślad stosu oryginalnego wyjątku. Powinieneś po prostu „rzucać”. Lub utwórz nowy wyjątek i ustaw wyjątek InnerException na „e”, zanim go wyrzucisz.
— Erv Walter,

24
w końcu byłby to bardzo kiepski wybór słowa kluczowego, gdyby nie działało jako ostatnie , prawda?
— Eric Lippert

@ErvWalter czy to nadal prawda? Testuję to w obie strony w VS2017 i wygląda na to, że jest dokładnie taki sam. Czy możesz podać więcej informacji lub odniesienie? Dzięki
— Jeff Puckett

tylko sugestia nazwy użyj Wyjątek ex - zarezerwuj e dla wydarzeń / delegatów
— pan R

Odpowiedzi:


145

Zostałby wywołany po ponownym wyrzuceniu e (tj. Po wykonaniu bloku catch)

edytowanie tego 7 lat później - jedną ważną uwagą jest to, że jeśli enie zostanie przechwycony przez blok try / catch wyżej na stosie wywołań lub obsługiwany przez globalną procedurę obsługi wyjątków, finallyblok może w ogóle nie zostać wykonany.


18
i nigdy, jeśli zadzwonisz do Envrionment.FailFast ()
— Johannes Rudolph

17
Po wypróbowaniu kodu w odpowiedzi Brandona widzę, że finallyNIE jest wykonywany, jeśli wyjątek rzucony w poprzednim catchnigdy nie został przechwycony w zewnętrznym try- catchbloku!
— Andrew

4
Dziękujemy za zmodyfikowanie (zaakceptowanej) odpowiedzi w celu uwzględnienia nowych informacji.
— Gordon Bean

3
(Microsoft) mówi o tym w nowej witrynie z dokumentacją: docs.microsoft.com/en-us/dotnet/csharp/language-reference/… : „W ramach obsługiwanego wyjątku, skojarzony blok final z pewnością zostanie uruchomiony. Jednak , jeśli wyjątek nie jest obsługiwany, wykonanie ostatniego bloku zależy od tego, w jaki sposób zostanie wyzwolona operacja wycofania wyjątku. To z kolei zależy od konfiguracji komputera. "
— DotNetSparky

2
Zwróć uwagę, że „przechwycony przez blok try / catch wyżej na stosie wywołań” będzie obejmował programy obsługi struktury, takie jak te w ASP.NET lub moduł uruchamiający testy. Lepszym sposobem na umieszczenie tego może być stwierdzenie: „jeśli program będzie kontynuował działanie po bloku catch, zostanie wykonany ostatni blok”.
— ArrowCase

94

Dlaczego nie spróbować:

outer try
inner try
inner catch
inner finally
outer catch
outer finally

z kodem (sformatowany dla spacji pionowej):

static void Main() {
    try {
        Console.WriteLine("outer try");
        DoIt();
    } catch {
        Console.WriteLine("outer catch");
        // swallow
    } finally {
        Console.WriteLine("outer finally");
    }
}
static void DoIt() {
    try {
        Console.WriteLine("inner try");
        int i = 0;
        Console.WriteLine(12 / i); // oops
    } catch (Exception e) {
        Console.WriteLine("inner catch");
        throw e; // or "throw", or "throw anything"
    } finally {
        Console.WriteLine("inner finally");
    }
}

5
+1, jeśli chodzi o coś tak prostego, naprawdę powinieneś spróbować tak, jak zrobił to Marc. GJ ilustruje to zagnieżdżonym try / catch / w końcu :)
— Allen Rice

1
@AllenRice po co próbować, skoro Marc już to zrobił, a ja mogę po prostu wygooglować, aby znaleźć odpowiedź Marca? A może lepiej, spróbuj sam, a następnie stwórz pytanie SO i odpowiedz sobie na nie z korzyścią dla innych.
— joshden

8
Pamiętaj, że jeśli nie złapiesz wyjątku w zewnętrznym zaczepie, wewnętrzny ostatecznie NIGDY nie zostanie wykonany !! W takim przypadku wynik toouter try inner try inner catch Unhandled Exception: System.DivideByZeroException...
— Andrew

1
@Andrew, masz rację. Wyjaśnienie można znaleźć tutaj MSDN Magazine 2008 wrzesień: Przetwarzanie nieobsłużonych wyjątków w środowisku CLR (aby otworzyć chm należy odblokować: Właściwości pliku -> Ogólne -> Odblokuj). Jeśli zmienisz zewnętrzny blok catch na „catch (ArgumentException)”, nikt w końcu nie będzie kontynuowany, ponieważ CLR nie może znaleźć żadnego „programu obsługi wyjątków, który zgodził się obsłużyć wyjątek” DivideByZeroException.
— vladimir

37

Po przeczytaniu wszystkich odpowiedzi tutaj wygląda na to, że ostateczna odpowiedź to zależy :

  • Jeśli ponownie wyrzucisz wyjątek w bloku catch, a ten wyjątek zostanie przechwycony w innym bloku catch, wszystko zostanie wykonane zgodnie z dokumentacją.

  • Jeśli jednak wyjątek ponownie wywołany jest nieobsługiwany, ostatecznie nigdy nie jest wykonywany.

Przetestowałem ten przykład kodu w VS2010 w / C # 4.0

static void Main()
    {
        Console.WriteLine("Example 1: re-throw inside of another try block:");

        try
        {
            Console.WriteLine("--outer try");
            try
            {
                Console.WriteLine("----inner try");
                throw new Exception();
            }
            catch
            {
                Console.WriteLine("----inner catch");
                throw;
            }
            finally
            {
                Console.WriteLine("----inner finally");
            }
        }
        catch
        {
            Console.WriteLine("--outer catch");
            // swallow
        }
        finally
        {
            Console.WriteLine("--outer finally");
        }
        Console.WriteLine("Huzzah!");

        Console.WriteLine();
        Console.WriteLine("Example 2: re-throw outside of another try block:");
        try
        {
            Console.WriteLine("--try");
            throw new Exception();
        }
        catch
        {
            Console.WriteLine("--catch");
            throw;
        }
        finally
        {
            Console.WriteLine("--finally");
        }

        Console.ReadLine();
    }

Oto wynik:

Przykład 1: rzut ponownie do wnętrza innego bloku try:
--outer try
---- wewnętrzna próba
---- wewnętrzna złapanie
---- wewnętrzna ostatecznie -
zewnętrzna złapanie -
router wreszcie
Huzzah!

Przykład 2: ponowne wyrzucenie poza inny blok try:
--try
--catch

Nieobsługiwany wyjątek: System.Exception: zgłoszono wyjątek typu „System.Exception”.
w ConsoleApplication1.Program.Main () w C: \ local source \ ConsoleApplication1 \ Program.cs: wiersz 53


3
Świetny połów, nie byłem tego świadomy!
— Andrew

1
Zwróć uwagę, że ostatnia w końcu może zostać uruchomiona, w zależności od tego, co wybierzesz: stackoverflow.com/a/46267841/480982
— Thomas Weller

2
Interesujące ... Na .NET Core 2.0 ostatnia część jest uruchamiana po nieobsługiwanym wyjątku.
— Mahdi Ghiasi

Co ciekawe, właśnie wykonałem test zarówno w .NET Core 2.0, jak i .NET Framework 4.6.1 i oba uruchamiają ostatecznie po nieobsługiwanym wyjątku. Czy to zachowanie się zmieniło?
— Cameron Bielstein

24

Twój przykład zachowałby się identycznie jak ten kod:

try {
    try {
        // Do stuff
    } catch(Exception e) {
        throw e;
    }
} finally {
    // Clean up
}

Na marginesie, jeśli naprawdę masz na myśli throw e;(to znaczy wyrzuć ten sam wyjątek, który właśnie złapałeś), znacznie lepiej jest po prostu to zrobić throw;, ponieważ zachowa to oryginalny ślad stosu zamiast tworzyć nowy.


Nie sądzę, żeby to było poprawne. Wreszcie powinien znajdować się wewnątrz zewnętrznego bloku
— próbnego, a

@MatthewPigram: Co masz na myśli? W rzeczywistości finallyblok będzie działał po catchbloku (nawet jeśli blok catch ponownie zgłosi wyjątek), co próbuje zilustrować mój fragment kodu.
— Daniel Pryden,

z tego, jak interpretuję jego przykład, próbuje złapać w końcu w innym bloku try. NOT a try catch in a try catch
— Matthew Pigram

1
@MatthewPigram: W mojej odpowiedzi nie ma żadnej konstrukcji typu „spróbuj złapać w końcu”. Zawiera „spróbuj-w końcu”, a wewnątrz tego trybloku moja odpowiedź ma „spróbuj-złapać”. Próbuję wyjaśnić zachowanie konstrukcji trzyczęściowej za pomocą dwóch konstrukcji dwuczęściowych. Nie widzę żadnego znaku drugiego trybloku w oryginalnym pytaniu, więc nie rozumiem, skąd to rozumiesz.
— Daniel Pryden,

12

Jeśli w bloku obsługi catch znajduje się nieobsługiwany wyjątek, ostatni blok zostanie wywołany dokładnie zero razy

  static void Main(string[] args)
  {
     try
     {
        Console.WriteLine("in the try");
        int d = 0;
        int k = 0 / d;
     }
     catch (Exception e)
     {
        Console.WriteLine("in the catch");
        throw;
     }
     finally
     {
        Console.WriteLine("In the finally");
     }
  }

Wynik:

C: \ users \ administrator \ documents \ TestExceptionNesting \ bin \ Release> TestExceptionNesting.exe

w próbie

w połowie

Nieobsługiwany wyjątek: System.DivideByZeroException: próbowano podzielić przez zero. at TestExceptionNesting.Program.Main (String [] args) w C: \ users \ administrator \ documents \ TestExceptionNesting \ TestExceptionNesting.cs: wiersz 22

C: \ users \ administrator \ documents \ TestExceptionNesting \ bin \ release>

Zadano mi to pytanie dzisiaj na wywiadzie, a ankieter ciągle powtarzał: „Czy na pewno w końcu nie zostanie wezwany?” Nie byłem pewien, czy chodziło o podchwytliwe pytanie, czy ankieter miał na myśli coś innego i napisał niewłaściwy kod do debugowania, więc wróciłem do domu i wypróbowałem go (kompilacja i uruchomienie, bez interakcji z debugerem), tylko po to, żeby się skupić reszta.


O ile rzucony wyjątek nie zostanie złapany w innym złapaniu gdzieś wyżej na stosie, w takim przypadku może on zostać wykonany, jeśli ten rzucony wyjątek zostanie obsłużony ... Albo mogę się mylić ...
— tomosius

@tomosius, tak, to wyjaśnia odpowiedź Brandona. :)
— Andrew

@tomosius Dlatego zacząłem od określenia „Jeśli istnieje nieobsługiwany wyjątek”. Jeśli rzucony wyjątek zostanie gdzieś złapany, to z definicji mówimy o innym przypadku.
— Eusebio Rufian-Zilbermann

To nie jest prawda. Przynajmniej nie dla NET Core 3.1. Prosty nowy projekt konsoli z tym kodem wyświetla „w końcu” po wyjątku.
— emzero

Ciekawe, że zachowanie się zmieniło, .NET core nawet nie istniał, kiedy pisałem;)
— Eusebio Rufian-Zilbermann

2

Prostym sposobem na to jest również debugowanie kodu i zauważenie, kiedy zostanie wywołana.


1

Testowanie za pomocą aplikacji konsoli C #, ostatecznie kod został wykonany po rzuceniu wyjątku: istniało „Okno dialogowe błędu aplikacji” i po wybraniu opcji „Zamknij program” ostatni blok został wykonany w tym oknie konsoli. Ale ustawiając punkt krytyczny wewnątrz ostatniego bloku kodu, nigdy nie mogę tego osiągnąć. Debugger zatrzymuje się na instrukcji throw. Oto mój kod testowy:

    class Program
    {
       static void Main(string[] args)
       {
          string msg;
          Console.WriteLine(string.Format("GetRandomNuber returned: {0}{1}", GetRandomNumber(out msg), msg) == "" ? "" : "An error has occurred: " + msg);
       }

       static int GetRandomNumber(out string errorMessage)
       {
         int result = 0;
         try
         {
            errorMessage = "";
            int test = 0;
            result = 3/test;
            return result;
         }
         catch (Exception ex)
         {
            errorMessage = ex.Message;
            throw ex;

         }
         finally
         {
            Console.WriteLine("finally block!");
         }

       }
    }

Debugowanie w VS2010 - .NET Framework 4.0

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.