Что произойдет, если блок finally генерирует исключение? - PullRequest
249 голосов
/ 26 мая 2010

Если блок finally генерирует исключение, что происходит точно ?

В частности, что происходит, если исключение выдается на полпути через блок finally. Вызваны ли остальные операторы (после) в этом блоке?

Я знаю, что исключения будут распространяться вверх.

Ответы [ 11 ]

397 голосов
/ 26 мая 2010

Если блок finally генерирует исключение, что происходит точно ?

Это исключение распространяется вверх и вверх и будет (может) обрабатываться на более высоком уровне.

Ваш блок finally будет не завершен после того, как будет сгенерировано исключение.

Если блок finally выполнялся во время обработки более раннего исключения, то это первое исключение теряется.

C # 4 Спецификация языка & sect; 8.9.5. Если блок finally вызывает другое исключение, обработка текущего исключения прекращается.

96 голосов
/ 26 мая 2010

Для таких вопросов я обычно открываю пустой проект консольного приложения в Visual Studio и пишу небольшой пример программы:

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");
        }
    }
}

Когда вы запустите программу, вы увидите точный порядок, в котором выполняются блоки catch и finally. Обратите внимание, что код в блоке finally после создания исключения не будет выполнен (фактически, в этом примере программы Visual Studio даже предупредит вас, что обнаружил недоступный код):

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

Дополнительное замечание

Как отметил Михаил Даматов, исключение из блока try будет "съедено", если вы не обработаете его в (внутреннем) блоке catch. Фактически, в приведенном выше примере повторно выброшенное исключение не появляется во внешнем блоке перехвата. Чтобы сделать это еще более ясным, взгляните на следующий слегка измененный образец:

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");
        }
    }
}

Как видно из выходных данных, внутреннее исключение "потеряно" (т.е. игнорируется):

Inner finally block
Outer catch block handling exception thrown from finally block.
Outer finally block
10 голосов
/ 26 мая 2010

Если есть исключение, ожидающее рассмотрения (когда блок try имеет finally, но не catch), новое исключение заменяет это.

Если нет ожидающих исключений, оно работает так же, как и исключение вне блока finally.

4 голосов
/ 26 мая 2010

Распространяется исключение.

2 голосов
/ 10 мая 2016

Быстрый (и довольно очевидный) фрагмент, чтобы сохранить «оригинальное исключение» (добавляется в блок try) и принести в жертву «окончательное исключение» (добавляется в блок finally), в случае, если для вас важнее оригинальное:

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

Когда код, приведенный выше, выполняется, «Исходное исключение» распространяется вверх по стеку вызовов, а «Окончательное исключение» теряется.

2 голосов
/ 14 июля 2015

Я должен был сделать это для отлова ошибки при попытке закрыть поток, который никогда не был открыт из-за исключения.

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();
    }
}

если webRequest был создан, но во время

произошла ошибка соединения
using (var sw = webRequest.GetRequestStream())

тогда finally будет ловить исключение, пытаясь закрыть соединения, которые, по его мнению, были открыты, потому что был создан веб-запрос.

Если, наконец, не было try-catch внутри, этот код вызовет необработанное исключение при очистке webRequest

if (webRequest.GetRequestStream() != null) 

оттуда код будет завершен без надлежащей обработки возникшей ошибки и, следовательно, возникновения проблем для вызывающего метода.

Надеюсь, это поможет в качестве примера

1 голос
/ 20 мая 2016

Исключение распространяется вверх и должно обрабатываться на более высоком уровне. Если исключение не обрабатывается на более высоком уровне, приложение вылетает. Выполнение блока finally останавливается в точке, где выдается исключение.

Независимо от того, есть ли исключение или нет, блок "finally" гарантированно будет выполнен.

  1. Если блок finally выполняется после возникновения исключения в блоке try,

  2. и если это исключение не обрабатывается

  3. и если блок finally генерирует исключение

Тогда исходное исключение, которое произошло в блоке try, теряется.

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");
        }
    }
} 

Отличная статья для деталей

1 голос
/ 03 ноября 2013

Несколько месяцев назад я тоже столкнулся с чем-то подобным,

    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();
        }

Чтобы решить эту проблему, я создал служебный класс, подобный

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;
        }
    }
}

И используется вот так

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);
}

но если вы хотите использовать параметры и возвращаемые типы, это другая история

1 голос
/ 29 ноября 2012

Создание исключения при активном другом исключении приведет к замене первого исключения вторым (более поздним) исключением.

Вот код, который иллюстрирует, что происходит:

    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);
        }
    }
  • Запустите код, и вы увидите «второе исключение»
  • Раскомментируйте операторы try и catch, и вы увидите «первое исключение»
  • Также раскомментируйте бросок; и вы снова увидите «второе исключение».
1 голос
/ 26 мая 2010
public void MyMethod()
{
   try
   {
   }
   catch{}
   finally
   {
      CodeA
   }
   CodeB
}

Способ обработки исключений, выданных CodeA и CodeB, одинаков.

Исключение, сгенерированное в блоке finally, не имеет ничего особенного, рассматривайте его как исключение с помощью кода B.

...