299

如果 finally 块抛出异常,究竟会发生什么?

具体来说,如果在 finally 块中途抛出异常会发生什么。是否会调用此块中的其余语句(之后)?

我知道异常会向上传播。

4

11 回答 11

450

如果 finally 块抛出异常究竟会发生什么?

该异常会向外传播,并且将(可以)在更高级别进行处理。

您的 finally 块将不会在引发异常的点之后完成。

如果 finally 块在处理较早的异常期间正在执行,则第一个异常将丢失。

C# 4 语言规范第 8.9.5 节:如果 finally 块抛出另一个异常,则终止当前异常的处理。

于 2010-05-26T08:26:36.173 回答
111

对于这样的问题,我通常会在 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 甚至会警告您它检测到无法访问的代码):

从 try 块抛出的内部 catch 块处理异常。
内部 finally 块
从 finally 块抛出的外部 catch 块处理异常。
外部 finally 块

附加说明

正如迈克尔·达马托夫(Michael 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");
        }
    }
}

从输出中可以看出,内部异常是“丢失”(即被忽略):

内部 finally 块
从 finally 块抛出的外部 catch 块处理异常。
外部 finally 块
于 2010-05-26T08:32:31.540 回答
10

如果有一个异常挂起(当try块有finally但没有catch),新的异常会替换那个。

如果没有挂起的异常,它就像在finally块外抛出异常一样工作。

于 2010-05-26T08:36:13.450 回答
6

try保存“原始异常”(在块中抛出)并牺牲“最终异常”(在块中抛出)的快速(而且相当明显)片段finally,以防原始异常对您更重要:

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

执行上述代码时,“原始异常”会向上传播调用堆栈,“最终异常”会丢失。

于 2016-05-09T22:55:43.580 回答
5

传播异常。

于 2010-05-26T08:26:06.220 回答
2

在另一个异常处于活动状态时引发异常将导致第一个异常被第二个(后来的)异常替换。

下面是一些说明发生了什么的代码:

    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;声明,您将再次看到“第二个异常”。
于 2012-11-29T06:25:56.770 回答
2

几个月前我也遇到过这样的事情,

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

但是如果你想使用参数和返回类型那就是另一回事了

于 2013-11-03T17:48:44.990 回答
2

我必须这样做是为了捕捉一个错误,试图关闭一个由于异常而从未打开的流。

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 会捕获一个异常,试图关闭它认为是打开的连接,因为 webRequest 已经创建。

如果 finally 里面没有 try-catch,这段代码会在清理 webRequest 时导致未处理的异常

if (webRequest.GetRequestStream() != null) 

从那里代码将退出而没有正确处理发生的错误,从而导致调用方法出现问题。

希望这有助于作为一个例子

于 2015-07-14T15:52:53.393 回答
2

异常向上传播,应在更高级别进行处理。如果异常未在更高级别处理,则应用程序崩溃。“finally”块执行在抛出异常的地方停止。

不管是否有异常,“finally”块都保证执行。

  1. 如果在 try 块中发生异常后正在执行“finally”块,

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

详细信息的好文章

于 2016-05-20T03:55:51.280 回答
1
public void MyMethod()
{
   try
   {
   }
   catch{}
   finally
   {
      CodeA
   }
   CodeB
}

CodeA 和 CodeB 抛出的异常的处理方式是一样的。

块中抛出的异常finally没有什么特别之处,将其视为代码 B 抛出的异常。

于 2010-05-26T08:28:05.983 回答
-2

它引发异常;)您可以在其他一些 catch 子句中捕获该异常。

于 2010-05-26T08:26:21.367 回答