簡體   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?

當一個異常可以在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塊中異常的直接結果,則返回該異常只會使實際問題變得復雜或模糊,使其難以解決。 在極少數情況下,存在返回的驗證原因(例如異常),那么使用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()方法時,也可以拋出必須以相同方式處理的AgregateExceptions。

關於@ damien的注釋的另一個注意事項:只有在finally塊拋出的情況下才會將異常包裝到AggregateException中。 沒有其他任何事情與異常相關,也不以任何方式處理。

對於那些想要這樣做的人,你可以使用我最近創建的一個小助手類:

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

並像這樣使用它:

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM