简体   繁体   English

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

[英]What is the best practice in C# to propagate an exception thrown in a finally block without losing an exception from a catch block?

When an exception is possible to be thrown in a finally block how to propagate both exceptions - from catch and from finally? 当一个异常可以在finally块中抛出时如何传播两个异常 - 来自catch和finally?

As a possible solution - using an AggregateException: 作为一种可能的解决方案 - 使用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;
            }
        }
    }
}

These exceptions can be handled like in following snippet: 可以像下面的代码段一样处理这些异常:

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

As the comments have suggested this may indicate "unfortunately" structured code. 正如评论所暗示的那样,这可能表示“不幸”的结构化代码。 For example if you find yourself in this situation often it might indicate that you are trying to do too much within your method. 例如,如果您经常发现自己处于这种情况,则可能表明您正在尝试在您的方法中做太多。 You only want to throw and exception if there is nothing else you can do (your code is 'stuck' with a problem you can't program around. You only want to catch an exception if there is a reasonable expectation you can do something useful. There is an OutOfMemoryException in the framework but you will seldom see people trying to catch it, because for the most part it means you're boned :-) 如果没有其他任何你可以做的事情,你只想抛出异常(你的代码被“卡住”了一个你无法编程的问题。如果有合理的期望你可以做一些有用的事情,你只想抓住一个例外框架中有一个OutOfMemoryException,但你很少会看到有人试图抓住它,因为在很大程度上它意味着你是骨头的:-)

If the exception in the finally block is a direct result of the exception in the try block, returning that exception just complicates or obscures the real problem, making it harder to resolve. 如果finally块中的异常是try块中异常的直接结果,则返回该异常只会使实际问题变得复杂或模糊,使其难以解决。 In the rare case where there is a validate reason for returning such as exception then using the AggregateException would be the way to do it. 在极少数情况下,存在返回的验证原因(例如异常),那么使用AggregateException将是这样做的方法。 But before taking that approach ask yourself if it's possible to separate the exceptions into separate methods where a single exception can be returned and handled (separately). 但在采用这种方法之前,请问自己是否可以将异常分成单独的方法,其中可以返回和处理单个异常(单独处理)。

I regularly come into the same situation and have not found a better solution yet. 我经常遇到同样的情况,还没有找到更好的解决方案。 But I think the solution suggested by the OP is eligible. 但我认为OP建议的解决方案是合格的。

Here's a slight modification of the original example: 这是对原始示例的略微修改:

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

IMHO it will be fatal to ignore one or the other exception here. 恕我直言,忽略这里的一个或另一个例外是致命的。

Another example: Imagin a test system that calibrates a device (DUT) and therefore has to control another device that sends signals to the DUT. 另一个例子:想象一个校准设备(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;
            }
        }
    }
}

In this example, it is important that all devices are set to a valid state after the procedure. 在此示例中,重要的是在过程之后将所有设备设置为有效状态。 But both devices also can throw exceptions in the finally block that must not get lost or ignored. 但是这两个设备也可以在finally块中抛出异常,这些异常不会丢失或被忽略。

Regarding the complexity in the caller, I do not see any problem there either. 关于呼叫者的复杂性,我也没有看到任何问题。 When using System.Threading.Tasks the WaitAll() method, for example, can also throw AgregateExceptions that have to be handled in the same way. 例如,当使用System.Threading.Tasks WaitAll()方法时,也可以抛出必须以相同方式处理的AgregateExceptions。

One more note regarding @damien's comment: The exception is only caught to wrap it into the AggregateException, in case that the finally block throws. 关于@ damien的注释的另一个注意事项:只有在finally块抛出的情况下才会将异常包装到AggregateException中。 Nothing else is done with the exception nor is it handled in any way. 没有其他任何事情与异常相关,也不以任何方式处理。

For those who want to go this way you can use a little helper class I created recently: 对于那些想要这样做的人,你可以使用我最近创建的一个小助手类:

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

and use it like this: 并像这样使用它:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在C#中,如果关联的catch块引发异常,是否可以强制控制通过finally块? - in C# is it possible to force control to pass through a finally block if an exception is thrown by an associated catch block? 在 catch/finally 块中抛出的吞咽异常 - Swallowing exception thrown in catch/finally block C#异常处理最后在catch块之前阻塞 - C# exception handling finally block before catch block 在finally块中引发异常 - Get thrown exception in finally block 为什么抛出的异常与catch块C#没有正确匹配 - Why thrown exception is not correctly matched with catch block C# 在finally块中抛出异常后,返回值会发生什么? - What happens to the returned value after exception is thrown in finally block? 处理异常的最佳实践,它在catch块中抛出,在一个线程中。 (。净) - Best practice to handle exception, that is thrown within catch block, in a thread. (.NET) C#中的3个catch块变体之间有什么区别(&#39;Catch&#39;,&#39;Catch(Exception)&#39;和&#39;Catch(Exception e)&#39;)? - What is the difference between the 3 catch block variants in C# ( 'Catch', 'Catch (Exception)', and 'Catch(Exception e)' )? C# 异常处理 - 上次捕获块获取是否会重新抛出异常? - C# Exception Handling - will last catch block fetch re-thrown Exception? Selenium C# (NUnit) - 异常未在 catch 块中捕获,因为即使抛出异常也会执行 [onetimeteardown] - Selenium C# (NUnit)- Exception not caught in catch block as [onetimeteardown] executes even if exception thrown
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM