[英]in C# is it possible to force control to pass through a finally block if an exception is thrown by an associated catch block?
[英]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.