Что произойдет, если блок finally выдает исключение?

Если блок finally выдает исключение, что ровно происходит?

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

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

11 ответов


Если блок finally выдает исключение what ровно происходит ?

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

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

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

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


для таких вопросов я обычно открываю пустой проект консольного приложения в 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

Дополнительная Информация

как Майкл Damatov указал, исключение из try блок будет "съеден", если вы не обработаете его в (внутреннем) catch блок. На самом деле, в приведенном выше примере повторное исключение не отображается во внешнем блоке 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

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

если нет ожидающего исключения, он работает так же, как и исключение за пределами finally блок.


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


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

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

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


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

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

затем, наконец, поймает исключение, пытающееся закрыть соединения, которые он считал открытыми, потому что был создан webRequest.

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

if (webRequest.GetRequestStream() != null) 

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

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


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

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

    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, и вы увидите "первое исключение"
  • также раскомментируйте throw; оператор, и вы увидите " второе исключение" снова.

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

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

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


public void MyMethod()
{
   try
   {
   }
   catch{}
   finally
   {
      CodeA
   }
   CodeB
}

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

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


исключение распространяется вверх и должно обрабатываться на более высоком уровне. Если исключение не обрабатывается на более высоком уровне, приложение аварийно завершает работу. Выполнение блока "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");
        }
    }
} 

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


Он создает исключение ;) вы можете поймать это исключение в каком-то другом предложении catch.