Co się stanie, jeśli blok finally wyrzuci wyjątek?

Jeśli w końcu blok wyrzuca wyjątek, co dokładnie się dzieje?

W szczególności, co się stanie, jeśli wyjątek zostanie wyrzucony w połowie bloku finally. Czy reszta poleceń (po) w tym bloku zostanie wywołana?

Zdaję sobie sprawę, że wyjątki będą propagować się w górę.

Author: computingfreak, 2010-05-26

11 answers

Jeśli w końcu blok wyrzuci wyjątek, co Dokładnie się stanie ?

Ten wyjątek rozprzestrzenia się i rośnie, i będzie (może) być obsługiwany na wyższym poziomie.

Twój ostatni blok nie zostanie zakończony poza punktem, w którym wyrzucony jest wyjątek.

Jeśli blok finally był wykonywany podczas obsługi wcześniejszego wyjątku, to ten pierwszy wyjątek zostanie utracony.

Specyfikacja języka C# 4 § 8.9.5: jeśli w końcu blok rzuca inny wyjątek, przetwarzanie bieżącego wyjątku jest zakończone.

 367
Author: Henk Holterman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-29 12:51:58

W przypadku takich pytań Zwykle otwieram pusty projekt aplikacji konsolowej w 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ść wykonywania bloków catch i finally. Należy pamiętać, że kod w bloku finally po wyrzuceniu wyjątku nie zostanie wykonany (w rzeczywistości w tym przykładowym programie Visual Studio ostrzega nawet, że wykrył kod nieosiągalny):

Inner catch block handling exception thrown from try block.
Inner finally block
Outer catch block handling exception thrown from finally block.
Outer finally block

Dodatkowe Uwaga

Jak zauważył Michael Damatov, wyjątek z try bloku zostanie "zjedzony", jeśli nie obsłużysz go w (wewnętrznym) catch bloku. W rzeczywistości w powyższym przykładzie wyjątek re-throwed nie pojawia się w zewnętrznym bloku catch. Aby uczynić to jeszcze bardziej przejrzystym, spójrz na poniższą 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ć na wyjściu wewnętrzny wyjątek jest "utracony" (tzn. ignorowany):

Inner finally block
Outer catch block handling exception thrown from finally block.
Outer finally block
 94
Author: Dirk Vollmar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-26 09:38:39

Jeśli istnieje wyjątek oczekujący (gdy try blok ma finally, ale nie catch), nowy wyjątek zastępuje ten.

Jeśli nie ma wyjątku oczekującego, działa to tak, jak wyrzucenie wyjątku poza blok finally.

 10
Author: Guffa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-11 12:35:36

Wyjątek jest propagowany.

 3
Author: Darin Dimitrov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-26 08:26:06

Szybki (i dość oczywisty) fragment, aby zapisać "oryginalny wyjątek" (wrzucony do bloku try) i poświęcić "w końcu wyjątek" (wrzucony do bloku finally), jeśli oryginalny jest dla ciebie ważniejszy:

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

Gdy powyższy kod jest wykonywany, "oryginalny wyjątek" propaguje się w górę stosu wywołań, a "w końcu wyjątek" jest tracony.

 2
Author: lxa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-09 22:55:43

Musiałem to zrobić, aby złapać 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 wystąpił błąd połączenia podczas

using (var sw = webRequest.GetRequestStream())

Wtedy wreszcie złapał wyjątek próbujący zamknąć połączenia, które uważał za otwarte, ponieważ webRequest został utworzony.

Jeśli finally didnt have a try-catch inside, ten kod spowoduje nieobsługiwany wyjątek podczas czyszczenia webRequest

if (webRequest.GetRequestStream() != null) 

Stamtąd kod zakończyłby się bez poprawnej obsługi błędu, który się wydarzył, a tym samym powodowania problemów z wywołaniem metody.

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

 2
Author: Emma Grant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-10 05:59:10

Wyrzucenie wyjątku, gdy inny wyjątek jest aktywny, spowoduje, że pierwszy wyjątek zostanie zastąpiony drugim (późniejszym) wyjątkiem.

Oto kod, który ilustruje 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"
  • odkomentuj instrukcje try and catch, a zobaczysz "pierwszy wyjątek"
  • również odkomentuj polecenie throw;, a zobaczysz" drugi wyjątek".
 1
Author: Doug Coburn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-29 06:25:56

Kilka miesięcy temu też miałem do czynienia 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 zrobił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żyć parametrów i zwracać typy, to jest inna historia

 1
Author: Dipon Roy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-03 17:54:17
public void MyMethod()
{
   try
   {
   }
   catch{}
   finally
   {
      CodeA
   }
   CodeB
}

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

Wyjątek rzucony w bloku finally nie ma nic specjalnego, traktuj go jako wyjątek rzucony kodem B.

 1
Author: Danny Chen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-13 20:01:17

Wyjątek propaguje się i powinien być obsługiwany na wyższym poziomie. Jeśli wyjątek nie jest obsługiwany na wyższym poziomie, aplikacja ulega awarii. Wykonanie bloku "finally" zatrzymuje się w punkcie, w którym wyrzucany jest wyjątek.

Niezależnie od tego, czy istnieje wyjątek, czy nie, blok" finally " jest gwarantowany do wykonania.

  1. Jeśli blok "finally" jest wykonywany po wystąpieniu wyjątku w bloku try,

  2. A jeśli ten wyjątek nie jest obsługiwane

  3. I jeśli ostatecznie blok wyrzuci wyjątek

Wtedy oryginalny wyjątek, który wystąpił w bloku try zostanie 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ł o szczegółach

 1
Author: Raj Baral,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-16 17:09:57

Rzuca wyjątek ;) możesz złapać ten wyjątek w innej klauzuli catch.

 -1
Author: JHollanti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-26 08:26:21