ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

我知道异常会向上传播。

为什么不试试呢?但是在这种事情上,我最喜欢的是在 finally 之前返回,然后从 finally 块中返回其他东西。 :)
finally 块中的所有语句都必须执行。它不能有回报。 msdn.microsoft.com/en-us/library/0hbbzekw(VS.80).aspx

H
Henk Holterman

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

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

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

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

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


除非它是 ThreadAbortException,否则整个 finally 块将首先完成,因为它是一个关键部分。
@Shedal - 你是对的,但这仅适用于“某些异步异常”,即 ThreadAbortException。对于普通的 1 线程代码,我的答案成立。
“第一个异常丢失” - 这实际上非常令人失望,我偶然发现 IDisposable 对象在 Dispose() 中抛出异常,这导致异常在“using”子句中丢失。
“我发现 IDisposable 对象在 Dispose() 中抛出异常” - 至少可以说这很奇怪。在 MSDN 上阅读:AVOID throwing an exception from within Dispose(bool) except under ...
@HenkHolterman:磁盘满错误在直接连接的主硬盘上并不常见,但程序有时会将文件写入可移动或网络磁盘;这些问题可能更常见。如果有人在文件完全写入之前拔出 U 盘,最好立即告诉他们,而不是等到他们到达目的地并发现文件已损坏。在有错误时屈服于较早的错误可能是明智的行为,但是当没有较早的错误时,报告问题比不报告问题要好。
D
Dirk Vollmar

对于这样的问题,我通常会在 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");
        }
    }
}

当您运行程序时,您将看到 catchfinally 块执行的确切顺序。请注意,在抛出异常后的 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

附加说明

正如 Michael Damatov 所指出的,如果您不在(内部)catch 块中处理它,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

因为您在内部捕获中抛出异常,所以在此示例中永远不会到达“内部 finally 块”
@Theofanis Pantelides:不,一个 finally 块将(几乎)总是被执行,这在这种情况下也适用于内部 finally 块(只需自己尝试示例程序(在 a 的情况下将不会执行 finally 块)不可恢复的异常,例如 EngineExecutionException,但在这种情况下,您的程序无论如何都会立即终止)。
不过,我看不出在您的第一段代码的第一个捕获中抛出的作用是什么。我尝试使用控制台应用程序使用和不使用它,但没有发现任何差异。
@johnpan:重点是表明 finally 块总是执行,即使 try 和 catch 块都抛出异常。控制台输出确实没有区别。
M
Mudassir Hasan

如果存在未决的异常(当 try 块有 finally 但没有 catch 时),则新的异常会替换该异常。

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


如果存在一个匹配的 catch 块(重新)抛出异常,则异常也可能处于未决状态。
l
lxa

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

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

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


D
Darin Dimitrov

传播异常。


@bitbonk:像往常一样,由内而外。
D
Doug Coburn

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

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

    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;声明,您将再次看到“第二个异常”。


值得注意的是,清理“严重”异常是可能的,该异常只能在特定代码块之外捕获,以引发在其中捕获和处理的异常。使用异常过滤器(在 vb.net 中可用,但不是 C#)可以检测到这种情况。代码无法“处理”它,但如果使用任何类型的日志记录框架,它几乎肯定值得记录。在清理过程中发生异常触发系统崩溃的 C++ 方法是丑陋的,但是让异常消失是恕我直言的可怕之处。
n
nawfal

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

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

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


R
Raj Baral

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

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

如果在 try 块中发生异常之后正在执行“finally”块,并且如果未处理该异常并且 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");
        }
    }
} 

Great article for Details


M
Massimiliano Kraus

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

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) 

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

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


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

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

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


你能详细说明一下吗?你是什么意思与例外是一样的?
J
JHollanti

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