Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/335.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在C#中,传播finally块中抛出的异常而不丢失catch块中的异常的最佳实践是什么?_C#_Exception Handling_Try Catch Finally - Fatal编程技术网

在C#中,传播finally块中抛出的异常而不丢失catch块中的异常的最佳实践是什么?

在C#中,传播finally块中抛出的异常而不丢失catch块中的异常的最佳实践是什么?,c#,exception-handling,try-catch-finally,C#,Exception Handling,Try Catch Finally,当一个异常可能在finally块中抛出时,如何传播这两个异常-从catch和从finally 作为可能的解决方案-使用AggregateException: internal class MyClass { public void Do() { Exception exception = null; try { //example of an error occured in main logic

当一个异常可能在finally块中抛出时,如何传播这两个异常-从catch和从finally

作为可能的解决方案-使用AggregateException:

internal class MyClass
{
    public void Do()
    {
        Exception exception = null;
        try
        {
            //example of an error occured in main logic
            throw new InvalidOperationException();
        }
        catch (Exception e)
        {
            exception = e;
            throw;
        }
        finally
        {
            try
            {
                //example of an error occured in finally
                throw new AccessViolationException();
            }
            catch (Exception e)
            {
                if (exception != null)
                    throw new AggregateException(exception, e);
                throw;
            }
        }
    }
}
可以像以下代码段中那样处理这些异常:

private static void Main(string[] args)
{
    try
    {
        new MyClass().Do();
    }
    catch (AggregateException e)
    {
        foreach (var innerException in e.InnerExceptions)
            Console.Out.WriteLine("---- Error: {0}", innerException);
    }
    catch (Exception e)
    {
        Console.Out.WriteLine("---- Error: {0}", e);
    }

    Console.ReadKey();
}

正如评论所建议的,这可能表示“不幸的”结构化代码。例如,如果您发现自己经常处于这种情况,这可能表明您试图在您的方法中做得太多。只有在您无能为力的情况下,才需要抛出和异常(您的代码被无法编程的问题“卡住”了。您只希望在合理预期您可以做一些有用的事情时捕获异常。框架中存在OutOfMemoryException,但您很少会看到有人试图捕获它,因为在大多数情况下,这意味着您已筋疲力尽:-)


如果finally块中的异常是try块中异常的直接结果,则返回该异常只会使实际问题变得复杂或模糊,从而使问题更难解决。在极少数情况下,如果返回异常(如exception)有验证原因,则使用AggregateException将是一种方法。但在我们采用这种方法,问问自己是否有可能将异常分为单独的方法,其中一个异常可以(单独)返回和处理。

我经常遇到同样的情况,还没有找到更好的解决方案。但我认为OP建议的解决方案是合适的

以下是对原始示例的轻微修改:

internal class MyClass
{
    public void Do()
    {
        bool success = false;
        Exception exception = null;
        try
        {
            //calling a service that can throw an exception
            service.Call();
            success = true;
        }
        catch (Exception e)
        {
            exception = e;
            throw;
        }
        finally
        {
            try
            {
                //reporting the result to another service that also can throw an exception
                reportingService.Call(success);
            }
            catch (Exception e)
            {
                if (exception != null)
                    throw new AggregateException(exception, e);
                throw;
            }
        }
    }
}
在这里忽略一个或另一个例外是致命的

另一个例子:想象一个校准设备(DUT)的测试系统,因此必须控制另一个向DUT发送信号的设备

internal class MyClass
{
    public void Do()
    {
        Exception exception = null;
        try
        {
            //perform a measurement on the DUT
            signalSource.SetOutput(on);
            DUT.RunMeasurement();
        }
        catch (Exception e)
        {
            exception = e;
            throw;
        }
        finally
        {
            try
            {
                //both devices have to be set to a valid state at end of the procedure, independent of if any exception occurred
                signalSource.SetOutput(off);
                DUT.Reset();
            }
            catch (Exception e)
            {
                if (exception != null)
                    throw new AggregateException(exception, e);
                throw;
            }
        }
    }
}
在本例中,重要的是所有设备在过程结束后都设置为有效状态。但这两个设备也可以在finally块中抛出异常,这些异常不能丢失或忽略

关于调用方的复杂性,我也没有发现任何问题。例如,当使用System.Threading.Tasks时,WaitAll()方法也可以抛出必须以相同方式处理的AgegateException

关于@damien的评论,还有一点需要注意:捕获异常只是为了将其包装到AggregateException中,以防finally块抛出。对异常不做任何其他操作,也不以任何方式处理它

对于那些想这样做的人,可以使用我最近创建的一个小助手类:

public static class SafeExecute
{
    public static void Invoke(Action tryBlock, Action finallyBlock, Action onSuccess = null, Action<Exception> onError = null)
    {
        Exception tryBlockException = null;

        try
        {
            tryBlock?.Invoke();
        }
        catch (Exception ex)
        {
            tryBlockException = ex;
            throw;
        }
        finally
        {
            try
            {
                finallyBlock?.Invoke();
                onSuccess?.Invoke();
            }
            catch (Exception finallyBlockException)
            {
                onError?.Invoke(finallyBlockException);

                // don't override the original exception! Thus throwing a new AggregateException containing both exceptions.
                if (tryBlockException != null)
                    throw new AggregateException(tryBlockException, finallyBlockException);

                // otherwise re-throw the exception from the finally block.
                throw;
            }
        }
    }
}

我想不出一个有意义的场景,需要知道这两个异常。您的解决方案肯定会起作用,但在调用者中处理它会非常复杂。我认为在最后(在finally catch块中)重新抛出将永远不会到达,因为如果发生异常,对象e永远不会为null。如果捕获异常,您会说“我知道如何修复此问题”-如果您不知道如何解决此问题,那么您需要尽可能少地掩盖错误情况。我希望至少显示或记录这些异常。@SergeySmolnikov如果这就是您正在做的,那么您应该使用事件。同意,在使用此解决方案之前,应该修改代码设计。我尝试找到重新保存方式,而不是使用带有“try catch with empty catch block”的finally块。若要添加到@Dweeberly的答案中,通常使用finally块来执行任何资源清理。结构良好的代码不会尝试在finally块中执行任何恢复或进一步处理。finally是作为您的代码在异常传播到堆栈之前的最后一次尝试而提供的。俗话说“权力大,责任大”,用“最后”wisely@AbhijeetPatel:某些类型的类进入清理可能需要失败操作的状态是正常的(例如,可以构造一个
文件
类,其中每个写入数据方法都会确保在返回之前写入数据,但与使用延迟写入的类相比,性能会非常糟糕)。通常,
finally
中的失败比
try
中的失败更严重,但是很少有好的方法可以知道在任何特定情况下这是否正确。我强烈不同意这表示代码结构不良。一个简单的例子是,当您需要在finally块中处理某些内容,并且希望确保不管在那块代码之前是否有任何异常,处理本身也进行得很顺利“如果最终不必处理try/catch/finally,就很难将其划分为不同的范围。那么,当我们无论如何都要面对这个问题时,我们为什么要逃避这个问题呢。
public void ExecuteMeasurement(CancellationToken cancelToken)
{
    SafeExecute.Invoke(
        () => DUT.ExecuteMeasurement(cancelToken),
        () =>
        {
            Logger.Write(TraceEventType.Verbose, "Save measurement results to database...");
            _Db.SaveChanges();
        },
        () => TraceLog.Write(TraceEventType.Verbose, "Done"));
}