Co się stanie, jeśli w końcu blok zgłosi wyjątek?

266

Jeśli w końcu blok zgłosi wyjątek, co dokładnie się stanie?

W szczególności, co się stanie, jeśli wyjątek zostanie wyrzucony w połowie ostatniego bloku. Czy reszta instrukcji (po) w tym bloku jest wywoływana?

Wiem, że wyjątki będą się rozprzestrzeniać w górę.

Jack Kada
źródło
8
Dlaczego nie spróbować? Ale w tego rodzaju rzeczach najbardziej podoba mi się powrót przed końcem, a następnie zwrócenie czegoś innego z bloku w końcu. :)
ANeves
8
Wszystkie instrukcje w bloku na końcu muszą zostać wykonane. To nie może mieć zwrotu. msdn.microsoft.com/en-us/library/0hbbzekw(VS.80).aspx
Tim Scarborough

Odpowiedzi:

419

Jeśli w końcu blok zgłosi wyjątek, co dokładnie się stanie?

Ten wyjątek rozprzestrzenia się w górę i w górę i będzie (można) obsłużyć go na wyższym poziomie.

Twój wreszcie blok nie będzie zostanie ukończony poza punktem, w którym zostanie zgłoszony wyjątek.

Jeśli w końcu blok był wykonywany podczas obsługi wcześniejszego wyjątku, ten pierwszy wyjątek zostanie utracony.

C # 4 Specyfikacja języka § 8.9.5: Jeżeli blok ostatecznie zgłosi inny wyjątek, przetwarzanie bieżącego wyjątku zostaje zakończone.

Henk Holterman
źródło
9
O ile nie jest to ThreadAbortException, cały blok zostanie ostatecznie ukończony, ponieważ jest to sekcja krytyczna.
Dmytro Szewczenko
1
@Shedal - masz rację, ale dotyczy to tylko „pewnych wyjątków asynchronicznych”, tj. ThreadAbortException. W przypadku normalnego kodu 1-wątkowego moja odpowiedź jest zachowana.
Henk Holterman
„Pierwszy wyjątek został utracony” - to jest naprawdę bardzo rozczarowujące, akuratnie znajduję obiekty IDisposable, które zgłaszają wyjątek w Dispose (), co powoduje utratę wyjątku wewnątrz klauzuli „using”.
Alex Burtsev
„Znajduję obiekty IDisposable, które zgłaszają wyjątek w Dispose ()” - co najmniej dziwne. Czytaj na MSDN: UNIKAJ, rzucając wyjątek z wnętrza Dispose (bool), z wyjątkiem sytuacji, gdy ...
Henk Holterman
1
@HenkHolterman: Błędy zapełnienia dysku nie są bardzo częste na bezpośrednio podłączonym podstawowym dysku twardym, ale programy czasami zapisują pliki na dyskach wymiennych lub sieciowych; problemy mogą być z nimi znacznie częstsze. Jeśli ktoś wyciągnie pamięć USB, zanim plik zostanie w pełni zapisany, lepiej powiedzieć mu natychmiast, niż poczekać, aż dotrze do celu i stwierdzi, że plik jest uszkodzony. Poddanie się wcześniejszemu błędowi, gdy taki występuje, może być rozsądnym zachowaniem, ale gdy nie ma wcześniejszego błędu, lepiej zgłosić problem, niż pozostawić go niezgłoszonym.
supercat
101

W przypadku takich pytań zwykle otwieram pusty projekt aplikacji konsolowej w programie Visual Studio i piszę mały przykładowy program:

using System;

class Program
{
    static void Main(string[] args)
    {
        try
        {
            try
            {
                throw new Exception("exception thrown from try block");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Inner catch block handling {0}.", ex.Message);
                throw;
            }
            finally
            {
                Console.WriteLine("Inner finally block");
                throw new Exception("exception thrown from finally block");
                Console.WriteLine("This line is never reached");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Outer catch block handling {0}.", ex.Message);
        }
        finally
        {
            Console.WriteLine("Outer finally block");
        }
    }
}

Po uruchomieniu programu zobaczysz dokładną kolejność catch i finallybloki są wykonywane. Należy pamiętać, że kod w ostatnim bloku po zgłoszeniu wyjątku nie zostanie wykonany (w rzeczywistości w tym przykładowym programie Visual Studio ostrzega cię nawet, że wykrył nieosiągalny kod):

Wystąpił wyjątek obsługi wewnętrznego bloku catch z bloku try.
Wewnętrzny wreszcie blok
Wystąpił wyjątek obsługi zewnętrznego bloku catch z bloku ostatecznie.
Zewnętrzny wreszcie blok

Uwaga dodatkowa

Jak zauważył Michael Damatov, wyjątek od trybloku zostanie „zjedzony”, jeśli nie poradzisz sobie z nim w (wewnętrznym) catchbloku. W powyższym przykładzie ponownie zgłoszony wyjątek nie pojawia się w zewnętrznym bloku catch. Aby uczynić to jeszcze bardziej przejrzystym, spójrz na następującą nieznacznie zmodyfikowaną próbkę:

using System;

class Program
{
    static void Main(string[] args)
    {
        try
        {
            try
            {
                throw new Exception("exception thrown from try block");
            }
            finally
            {
                Console.WriteLine("Inner finally block");
                throw new Exception("exception thrown from finally block");
                Console.WriteLine("This line is never reached");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Outer catch block handling {0}.", ex.Message);
        }
        finally
        {
            Console.WriteLine("Outer finally block");
        }
    }
}

Jak widać z danych wyjściowych, wewnętrzny wyjątek został „utracony” (tzn. Zignorowany):

Wewnętrzny wreszcie blok
Wystąpił wyjątek obsługi zewnętrznego bloku catch z bloku ostatecznie.
Zewnętrzny wreszcie blok
Dirk Vollmar
źródło
2
Ponieważ wyrzucisz wyjątek w swoim wewnętrznym haczyku, w tym przykładzie nigdy nie zostanie osiągnięte „Wewnętrzny blok w końcu”
Theofanis Pantelides
4
@Theofanis Pantelides: Nie, finallyblok będzie (prawie) zawsze wykonywany, dotyczy to również wewnętrznego bloku na końcu (wystarczy wypróbować sam program przykładowy (Blok na końcu nie zostanie wykonany w przypadku niemożliwego do odzyskania) wyjątek, np. EngineExecutionException, ale w takim przypadku program i tak zostanie natychmiast zakończony)
Dirk Vollmar
1
Nie rozumiem jednak, jaka jest rola rzutu w pierwszym haczyku pierwszego fragmentu kodu. Próbowałem z aplikacją konsoli i bez niej, nie znaleziono różnic.
JohnPan
@ johnpan: Chodziło o to, aby pokazać, że blok w końcu zawsze się wykonuje, nawet jeśli oba próbują złapać blok, zgłaszając wyjątek. Rzeczywiście nie ma różnicy w wyjściu konsoli.
Dirk Vollmar
10

Jeśli oczekuje się na wyjątek (gdy tryblok ma znak „ finallyale nie” catch), nowy wyjątek zastępuje ten wyjątek.

Jeśli nie ma oczekującego wyjątku, działa on tak jak wyrzucenie wyjątku poza finallyblok.

Guffa
źródło
Wyjątkiem może być również czeka, jeśli nie jest pasujący catchblok że (ponownie) wyrzuca wyjątek.
stakx - nie wnosi już
4

Wyjątek jest propagowany.

Darin Dimitrov
źródło
2
@bitbonk: jak zwykle od wewnątrz.
Piskvor opuścił budynek
3

Szybki (i raczej oczywisty) fragment, aby zapisać „oryginalny wyjątek” (wrzucony w trybloku) i poświęcić „w końcu wyjątek” (wrzucony w finallybloku), na wypadek gdyby oryginalny był dla Ciebie ważniejszy:

try
{
    throw new Exception("Original Exception");
}
finally
{
    try
    {
        throw new Exception("Finally Exception");
    }
    catch
    { }
}

Po wykonaniu powyższego kodu „Original Exception” propaguje stos wywołań, a „Last Exception” zostaje utracony.

lxa
źródło
2

Musiałem to zrobić, aby wychwycić błąd podczas próby zamknięcia strumienia, który nigdy nie został otwarty z powodu wyjątku.

errorMessage = string.Empty;

try
{
    byte[] requestBytes = System.Text.Encoding.ASCII.GetBytes(xmlFileContent);

    webRequest = WebRequest.Create(url);
    webRequest.Method = "POST";
    webRequest.ContentType = "text/xml;charset=utf-8";
    webRequest.ContentLength = requestBytes.Length;

    //send the request
    using (var sw = webRequest.GetRequestStream()) 
    {
        sw.Write(requestBytes, 0, requestBytes.Length);
    }

    //get the response
    webResponse = webRequest.GetResponse();
    using (var sr = new StreamReader(webResponse.GetResponseStream()))
    {
        returnVal = sr.ReadToEnd();
        sr.Close();
    }
}
catch (Exception ex)
{
    errorMessage = ex.ToString();
}
finally
{
    try
    {
        if (webRequest.GetRequestStream() != null)
            webRequest.GetRequestStream().Close();
        if (webResponse.GetResponseStream() != null)
            webResponse.GetResponseStream().Close();
    }
    catch (Exception exw)
    {
        errorMessage = exw.ToString();
    }
}

jeśli webRequest został utworzony, ale podczas połączenia wystąpił błąd połączenia

using (var sw = webRequest.GetRequestStream())

wtedy w końcu wychwyciłby wyjątek próbujący zamknąć połączenia, które według niego były otwarte, ponieważ utworzono webRequest.

Gdyby w końcu nie było w środku try-catch, ten kod spowodowałby nieobsługiwany wyjątek podczas czyszczenia webRequest

if (webRequest.GetRequestStream() != null) 

stamtąd kod zakończyłby działanie bez prawidłowej obsługi występującego błędu, a tym samym powodując problemy dla metody wywołującej.

Mam nadzieję, że to pomoże jako przykład

Emma Grant
źródło
1

Zgłoszenie wyjątku, gdy aktywny jest inny wyjątek, spowoduje zastąpienie pierwszego wyjątku drugim (późniejszym) wyjątkiem.

Oto kod ilustrujący to, co się dzieje:

    public static void Main(string[] args)
    {
        try
        {
            try
            {
                throw new Exception("first exception");
            }
            finally
            {
                //try
                {
                    throw new Exception("second exception");
                }
                //catch (Exception)
                {
                    //throw;
                }
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
    }
  • Uruchom kod, a zobaczysz „drugi wyjątek”
  • Odznacz komentarz try i catch, a zobaczysz „pierwszy wyjątek”
  • Odkomentuj także rzut; oświadczenie, a ponownie zobaczysz „drugi wyjątek”.
Doug Coburn
źródło
Warto zauważyć, że możliwe jest usunięcie „poważnego” wyjątku, który zostałby wychwycony poza konkretnym blokiem kodu, w celu wyrzucenia wyjątku, który jest w nim wychwytywany i obsługiwany. Używając filtrów wyjątków (dostępnych w vb.net, ale nie w C #) można wykryć ten warunek. Kod nie może wiele zrobić, aby go „obsłużyć”, ale jeśli używa się jakiejkolwiek struktury rejestrowania, prawie na pewno warto się zalogować. Podejście C ++ polegające na tym, że wyjątki występujące w trakcie czyszczenia powodują załamanie systemu, jest brzydkie, ale znikanie wyjątków jest ohydne.
supercat
1

Kilka miesięcy temu spotkałem się z czymś takim,

    private  void RaiseException(String errorMessage)
    {
        throw new Exception(errorMessage);
    }

    private  void DoTaskForFinally()
    {
        RaiseException("Error for finally");
    }

    private  void DoTaskForCatch()
    {
        RaiseException("Error for catch");
    }

    private  void DoTaskForTry()
    {
        RaiseException("Error for try");
    }


        try
        {
            /*lacks the exception*/
            DoTaskForTry();
        }
        catch (Exception exception)
        {
            /*lacks the exception*/
            DoTaskForCatch();
        }
        finally
        {
            /*the result exception*/
            DoTaskForFinally();
        }

Aby rozwiązać taki problem, stworzyłem klasę użyteczności jak

class ProcessHandler : Exception
{
    private enum ProcessType
    {
        Try,
        Catch,
        Finally,
    }

    private Boolean _hasException;
    private Boolean _hasTryException;
    private Boolean _hasCatchException;
    private Boolean _hasFinnallyException;

    public Boolean HasException { get { return _hasException; } }
    public Boolean HasTryException { get { return _hasTryException; } }
    public Boolean HasCatchException { get { return _hasCatchException; } }
    public Boolean HasFinnallyException { get { return _hasFinnallyException; } }
    public Dictionary<String, Exception> Exceptions { get; private set; } 

    public readonly Action TryAction;
    public readonly Action CatchAction;
    public readonly Action FinallyAction;

    public ProcessHandler(Action tryAction = null, Action catchAction = null, Action finallyAction = null)
    {

        TryAction = tryAction;
        CatchAction = catchAction;
        FinallyAction = finallyAction;

        _hasException = false;
        _hasTryException = false;
        _hasCatchException = false;
        _hasFinnallyException = false;
        Exceptions = new Dictionary<string, Exception>();
    }


    private void Invoke(Action action, ref Boolean isError, ProcessType processType)
    {
        try
        {
            action.Invoke();
        }
        catch (Exception exception)
        {
            _hasException = true;
            isError = true;
            Exceptions.Add(processType.ToString(), exception);
        }
    }

    private void InvokeTryAction()
    {
        if (TryAction == null)
        {
            return;
        }
        Invoke(TryAction, ref _hasTryException, ProcessType.Try);
    }

    private void InvokeCatchAction()
    {
        if (CatchAction == null)
        {
            return;
        }
        Invoke(TryAction, ref _hasCatchException, ProcessType.Catch);
    }

    private void InvokeFinallyAction()
    {
        if (FinallyAction == null)
        {
            return;
        }
        Invoke(TryAction, ref _hasFinnallyException, ProcessType.Finally);
    }

    public void InvokeActions()
    {
        InvokeTryAction();
        if (HasTryException)
        {
            InvokeCatchAction();
        }
        InvokeFinallyAction();

        if (HasException)
        {
            throw this;
        }
    }
}

I używane w ten sposób

try
{
    ProcessHandler handler = new ProcessHandler(DoTaskForTry, DoTaskForCatch, DoTaskForFinally);
    handler.InvokeActions();
}
catch (Exception exception)
{
    var processError = exception as ProcessHandler;
    /*this exception contains all exceptions*/
    throw new Exception("Error to Process Actions", exception);
}

ale jeśli chcesz używać parametrów i zwracać typy, to inna historia

Dipon Roy
źródło
1
public void MyMethod()
{
   try
   {
   }
   catch{}
   finally
   {
      CodeA
   }
   CodeB
}

Sposób obsługi wyjątków zgłaszanych przez CodeA i CodeB jest taki sam.

Wyjątek zgłoszony w finallybloku nie ma nic specjalnego, potraktuj go jak wyjątek wygenerowany przez kod B.

Cheng Chen
źródło
Czy mógłbyś opracować? Co masz na myśli mówiąc, że wyjątki są takie same?
Dirk Vollmar
1

Wyjątek rozprzestrzenia się w górę i powinien być obsługiwany na wyższym poziomie. Jeśli wyjątek nie jest obsługiwany na wyższym poziomie, aplikacja ulega awarii. Wykonywanie bloku „w końcu” kończy się w momencie zgłoszenia wyjątku.

Niezależnie od tego, czy istnieje wyjątek, czy blok „nareszcie” jest gwarantowany do wykonania.

  1. Jeśli blok „w końcu” jest wykonywany po wystąpieniu wyjątku w bloku try,

  2. a jeśli ten wyjątek nie zostanie obsłużony

  3. a jeśli w końcu blok zgłosi wyjątek

Następnie oryginalny wyjątek, który wystąpił w bloku try, zostaje utracony.

public class Exception
{
    public static void Main()
    {
        try
        {
            SomeMethod();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    public static void SomeMethod()
    {
        try
        {
            // This exception will be lost
            throw new Exception("Exception in try block");
        }
        finally
        {
            throw new Exception("Exception in finally block");
        }
    }
} 

Świetny artykuł dla szczegółów

Raj Baral
źródło
-1

Zgłasza wyjątek;) Możesz złapać ten wyjątek w innej klauzuli catch.

JHollanti
źródło