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.