对于C ++中的等效机制(析构函数),建议是it should usually not throw any exceptions。这主要是因为这样做可能会终止您的流程,这很少是一个好的策略。
在.NET的等效场景中......
...您的流程不会立即终止。但是,由于.NET无法用第二个异常替换第一个异常,因此会丢失信息。因此,调用堆栈上某处的catch块将永远不会出现第一个异常。然而,人们通常对第一个例外更感兴趣,因为这通常会为事情开始出错提供更好的线索。
由于.NET缺少一种机制来检测是否在异常处于挂起状态时正在执行代码,因此似乎只有两种选择可以实现IDisposable:
那么,哪两个邪恶中较小的一个?还有更好的方法吗?
编辑:为了澄清,我不是在谈论积极地抛出Dispose()或不抛出异常,我说的是让Dispose()调用的方法抛出的异常传播出Dispose ()或不,例如:
using System;
using System.Net.Sockets;
public sealed class NntpClient : IDisposable
{
private TcpClient tcpClient;
public NntpClient(string hostname, int port)
{
this.tcpClient = new TcpClient(hostname, port);
}
public void Dispose()
{
// Should we implement like this or leave away the try-catch?
try
{
this.tcpClient.Close(); // Let's assume that this might throw
}
catch
{
}
}
}
答案 0 :(得分:36)
Framework Design Guidelines(2 nd ed)将此作为(§9.4.1):
避免从Dispose(bool)中抛出异常,但在严重情况下除外 包含进程已损坏的情况(泄漏,不一致) 共享国家等。)。
评论[编辑]:
Dispose(bool)
,从终结器中抛出是一个坏主意并将阻止其他对象来自最终确定。我的视图:从Dispose中逃逸的异常应该只是那些,如指南中那样,足以造成灾难性的,以至于当前流程无法提供更可靠的功能。
答案 1 :(得分:17)
我认为吞咽是这种情况下两种邪恶中较小的一种,因为最好提高原始 Exception
- 警告:除非 ,也许干净地处理的失败本身就非常严重(可能是TransactionScope
无法处置,因为这可能表示回滚失败)。
有关此问题的更多想法,请参阅here - 包括包装/扩展方法的想法:
using(var foo = GetDodgyDisposableObject().Wrap()) {
foo.BaseObject.SomeMethod();
foo.BaseObject.SomeOtherMethod(); // etc
} // now exits properly even if Dispose() throws
当然,你可能会在原始和第二个(Dispose()
)异常重新抛出复合异常的情况下做一些奇怪的事情 - 但是想一想:你可以有多个using
块。它会很快变得无法管理。实际上,最初的例外是有趣的。
答案 2 :(得分:6)
Dispose
来实现其目的,处理对象。此任务是安全的,并且在大多数情况下不会抛出异常。如果你发现自己从Dispose
抛出异常,你可能应该三思而后行,看看你是否做了太多的东西。除此之外,我认为Dispose
应该像所有其他方法一样对待:处理如果你可以用它做某事,如果你做不到就让它冒泡。
编辑:对于指定的示例,我会编写代码,以便我的代码不会导致异常,但清除TcpClient
可能会导致异常,在我看来应该有效传播(或处理和重新抛出一个更通用的异常,就像任何方法一样):
public void Dispose() {
if (tcpClient != null)
tcpClient.Close();
}
但是,就像任何方法一样,如果你知道tcpClient.Close()
可能抛出一个应该被忽略的异常(无关紧要)或者应该被另一个异常对象表示,你可能想要抓住它。
答案 3 :(得分:2)
释放资源应该是一种“安全”操作 - 毕竟我怎样才能从无法释放资源中恢复?所以从Dispose中抛出异常是没有意义的。
但是,如果我在Dispose中发现程序状态已损坏,最好抛出异常然后吞下它,最好现在粉碎然后继续运行并产生不正确的结果。
答案 4 :(得分:2)
太糟糕了,微软没有向Dispose提供Exception参数,意图将它包装为InnerException,以防处理本身抛出异常。可以肯定的是,有效使用这样的参数需要使用C#不支持的异常过滤器块,但是这样的参数的存在是否可能促使C#设计者提供这样的功能?我希望看到的一个不错的变化是向Finally块添加Exception“参数”,例如。
finally Exception ex: // In C# Finally Ex as Exception ' In VB
这将表现得像普通的Finally块,除了'ex'将为null / Nothing如果'Try'运行完成,或者如果没有则保持抛出的异常。太糟糕了,没有办法让现有代码使用这样的功能。
答案 5 :(得分:1)
我可能会使用日志记录来捕获有关第一个异常的详细信息,然后允许引发第二个异常。
答案 6 :(得分:1)
有许多策略可以传播或吞噬Dispose
方法中的异常,可能是基于是否还从主逻辑中抛出了无法处理的异常。最佳解决方案是将决策权交给调用者,具体取决于他们的具体要求。我已经实现了这样做的通用扩展方法,提供:
using
例外Dispose
语义
Dispose
例外Dispose
例外AggregateException
AggregateException
中(如Task.Wait
一样)这是我的扩展方法:
/// <summary>
/// Provides extension methods for the <see cref="IDisposable"/> interface.
/// </summary>
public static class DisposableExtensions
{
/// <summary>
/// Executes the specified action delegate using the disposable resource,
/// then disposes of the said resource by calling its <see cref="IDisposable.Dispose()"/> method.
/// </summary>
/// <typeparam name="TDisposable">The type of the disposable resource to use.</typeparam>
/// <param name="disposable">The disposable resource to use.</param>
/// <param name="action">The action to execute using the disposable resource.</param>
/// <param name="strategy">
/// The strategy for propagating or swallowing exceptions thrown by the <see cref="IDisposable.Dispose"/> method.
/// </param>
/// <exception cref="ArgumentNullException"><paramref name="disposable"/> or <paramref name="action"/> is <see langword="null"/>.</exception>
public static void Using<TDisposable>(this TDisposable disposable, Action<TDisposable> action, DisposeExceptionStrategy strategy)
where TDisposable : IDisposable
{
ArgumentValidate.NotNull(disposable, nameof(disposable));
ArgumentValidate.NotNull(action, nameof(action));
ArgumentValidate.IsEnumDefined(strategy, nameof(strategy));
Exception mainException = null;
try
{
action(disposable);
}
catch (Exception exception)
{
mainException = exception;
throw;
}
finally
{
try
{
disposable.Dispose();
}
catch (Exception disposeException)
{
switch (strategy)
{
case DisposeExceptionStrategy.Propagate:
throw;
case DisposeExceptionStrategy.Swallow:
break; // swallow exception
case DisposeExceptionStrategy.Subjugate:
if (mainException == null)
throw;
break; // otherwise swallow exception
case DisposeExceptionStrategy.AggregateMultiple:
if (mainException != null)
throw new AggregateException(mainException, disposeException);
throw;
case DisposeExceptionStrategy.AggregateAlways:
if (mainException != null)
throw new AggregateException(mainException, disposeException);
throw new AggregateException(disposeException);
}
}
if (mainException != null && strategy == DisposeExceptionStrategy.AggregateAlways)
throw new AggregateException(mainException);
}
}
}
这些是已实施的策略:
/// <summary>
/// Identifies the strategy for propagating or swallowing exceptions thrown by the <see cref="IDisposable.Dispose"/> method
/// of an <see cref="IDisposable"/> instance, in conjunction with exceptions thrown by the main logic.
/// </summary>
/// <remarks>
/// This enumeration is intended to be used from the <see cref="DisposableExtensions.Using"/> extension method.
/// </remarks>
public enum DisposeExceptionStrategy
{
/// <summary>
/// Propagates any exceptions thrown by the <see cref="IDisposable.Dispose"/> method.
/// If another exception was already thrown by the main logic, it will be hidden and lost.
/// This behaviour is consistent with the standard semantics of the <see langword="using"/> keyword.
/// </summary>
/// <remarks>
/// <para>
/// According to Section 8.10 of the C# Language Specification (version 5.0):
/// </para>
/// <blockquote>
/// If an exception is thrown during execution of a <see langword="finally"/> block,
/// and is not caught within the same <see langword="finally"/> block,
/// the exception is propagated to the next enclosing <see langword="try"/> statement.
/// If another exception was in the process of being propagated, that exception is lost.
/// </blockquote>
/// </remarks>
Propagate,
/// <summary>
/// Always swallows any exceptions thrown by the <see cref="IDisposable.Dispose"/> method,
/// regardless of whether another exception was already thrown by the main logic or not.
/// </summary>
/// <remarks>
/// This strategy is presented by Marc Gravell in
/// <see href="http://blog.marcgravell.com/2008/11/dontdontuse-using.html">don't(don't(use using))</see>.
/// </remarks>
Swallow,
/// <summary>
/// Swallows any exceptions thrown by the <see cref="IDisposable.Dispose"/> method
/// if and only if another exception was already thrown by the main logic.
/// </summary>
/// <remarks>
/// This strategy is suggested in the first example of the Stack Overflow question
/// <see href="https://stackoverflow.com/q/1654487/1149773">Swallowing exception thrown in catch/finally block</see>.
/// </remarks>
Subjugate,
/// <summary>
/// Wraps multiple exceptions, when thrown by both the main logic and the <see cref="IDisposable.Dispose"/> method,
/// into an <see cref="AggregateException"/>. If just one exception occurred (in either of the two),
/// the original exception is propagated.
/// </summary>
/// <remarks>
/// This strategy is implemented by Daniel Chambers in
/// <see href="http://www.digitallycreated.net/Blog/51/c%23-using-blocks-can-swallow-exceptions">C# Using Blocks can Swallow Exceptions</see>
/// </remarks>
AggregateMultiple,
/// <summary>
/// Always wraps any exceptions thrown by the main logic and/or the <see cref="IDisposable.Dispose"/> method
/// into an <see cref="AggregateException"/>, even if just one exception occurred.
/// </summary>
/// <remarks>
/// This strategy is similar to behaviour of the <see cref="Task.Wait()"/> method of the <see cref="Task"/> class
/// and the <see cref="Task{TResult}.Result"/> property of the <see cref="Task{TResult}"/> class:
/// <blockquote>
/// Even if only one exception is thrown, it is still wrapped in an <see cref="AggregateException"/> exception.
/// </blockquote>
/// </remarks>
AggregateAlways,
}
样品使用:
new FileStream(Path.GetTempFileName(), FileMode.Create)
.Using(strategy: DisposeExceptionStrategy.Subjugate, action: fileStream =>
{
// Access fileStream here
fileStream.WriteByte(42);
throw new InvalidOperationException();
});
// Any Dispose() exceptions will be swallowed due to the above InvalidOperationException
更新:如果您需要支持返回值和/或异步的委托,那么您可以使用这些重载:
/// <summary>
/// Provides extension methods for the <see cref="IDisposable"/> interface.
/// </summary>
public static class DisposableExtensions
{
/// <summary>
/// Executes the specified action delegate using the disposable resource,
/// then disposes of the said resource by calling its <see cref="IDisposable.Dispose()"/> method.
/// </summary>
/// <typeparam name="TDisposable">The type of the disposable resource to use.</typeparam>
/// <param name="disposable">The disposable resource to use.</param>
/// <param name="strategy">
/// The strategy for propagating or swallowing exceptions thrown by the <see cref="IDisposable.Dispose"/> method.
/// </param>
/// <param name="action">The action delegate to execute using the disposable resource.</param>
public static void Using<TDisposable>(this TDisposable disposable, DisposeExceptionStrategy strategy, Action<TDisposable> action)
where TDisposable : IDisposable
{
ArgumentValidate.NotNull(disposable, nameof(disposable));
ArgumentValidate.NotNull(action, nameof(action));
ArgumentValidate.IsEnumDefined(strategy, nameof(strategy));
disposable.Using(strategy, disposableInner =>
{
action(disposableInner);
return true; // dummy return value
});
}
/// <summary>
/// Executes the specified function delegate using the disposable resource,
/// then disposes of the said resource by calling its <see cref="IDisposable.Dispose()"/> method.
/// </summary>
/// <typeparam name="TDisposable">The type of the disposable resource to use.</typeparam>
/// <typeparam name="TResult">The type of the return value of the function delegate.</typeparam>
/// <param name="disposable">The disposable resource to use.</param>
/// <param name="strategy">
/// The strategy for propagating or swallowing exceptions thrown by the <see cref="IDisposable.Dispose"/> method.
/// </param>
/// <param name="func">The function delegate to execute using the disposable resource.</param>
/// <returns>The return value of the function delegate.</returns>
public static TResult Using<TDisposable, TResult>(this TDisposable disposable, DisposeExceptionStrategy strategy, Func<TDisposable, TResult> func)
where TDisposable : IDisposable
{
ArgumentValidate.NotNull(disposable, nameof(disposable));
ArgumentValidate.NotNull(func, nameof(func));
ArgumentValidate.IsEnumDefined(strategy, nameof(strategy));
#pragma warning disable 1998
var dummyTask = disposable.UsingAsync(strategy, async (disposableInner) => func(disposableInner));
#pragma warning restore 1998
return dummyTask.GetAwaiter().GetResult();
}
/// <summary>
/// Executes the specified asynchronous delegate using the disposable resource,
/// then disposes of the said resource by calling its <see cref="IDisposable.Dispose()"/> method.
/// </summary>
/// <typeparam name="TDisposable">The type of the disposable resource to use.</typeparam>
/// <param name="disposable">The disposable resource to use.</param>
/// <param name="strategy">
/// The strategy for propagating or swallowing exceptions thrown by the <see cref="IDisposable.Dispose"/> method.
/// </param>
/// <param name="asyncFunc">The asynchronous delegate to execute using the disposable resource.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public static Task UsingAsync<TDisposable>(this TDisposable disposable, DisposeExceptionStrategy strategy, Func<TDisposable, Task> asyncFunc)
where TDisposable : IDisposable
{
ArgumentValidate.NotNull(disposable, nameof(disposable));
ArgumentValidate.NotNull(asyncFunc, nameof(asyncFunc));
ArgumentValidate.IsEnumDefined(strategy, nameof(strategy));
return disposable.UsingAsync(strategy, async (disposableInner) =>
{
await asyncFunc(disposableInner);
return true; // dummy return value
});
}
/// <summary>
/// Executes the specified asynchronous function delegate using the disposable resource,
/// then disposes of the said resource by calling its <see cref="IDisposable.Dispose()"/> method.
/// </summary>
/// <typeparam name="TDisposable">The type of the disposable resource to use.</typeparam>
/// <typeparam name="TResult">The type of the return value of the asynchronous function delegate.</typeparam>
/// <param name="disposable">The disposable resource to use.</param>
/// <param name="strategy">
/// The strategy for propagating or swallowing exceptions thrown by the <see cref="IDisposable.Dispose"/> method.
/// </param>
/// <param name="asyncFunc">The asynchronous function delegate to execute using the disposable resource.</param>
/// <returns>
/// A task that represents the asynchronous operation.
/// The task result contains the return value of the asynchronous function delegate.
/// </returns>
public static async Task<TResult> UsingAsync<TDisposable, TResult>(this TDisposable disposable, DisposeExceptionStrategy strategy, Func<TDisposable, Task<TResult>> asyncFunc)
where TDisposable : IDisposable
{
ArgumentValidate.NotNull(disposable, nameof(disposable));
ArgumentValidate.NotNull(asyncFunc, nameof(asyncFunc));
ArgumentValidate.IsEnumDefined(strategy, nameof(strategy));
Exception mainException = null;
try
{
return await asyncFunc(disposable);
}
catch (Exception exception)
{
mainException = exception;
throw;
}
finally
{
try
{
disposable.Dispose();
}
catch (Exception disposeException)
{
switch (strategy)
{
case DisposeExceptionStrategy.Propagate:
throw;
case DisposeExceptionStrategy.Swallow:
break; // swallow exception
case DisposeExceptionStrategy.Subjugate:
if (mainException == null)
throw;
break; // otherwise swallow exception
case DisposeExceptionStrategy.AggregateMultiple:
if (mainException != null)
throw new AggregateException(mainException, disposeException);
throw;
case DisposeExceptionStrategy.AggregateAlways:
if (mainException != null)
throw new AggregateException(mainException, disposeException);
throw new AggregateException(disposeException);
}
}
if (mainException != null && strategy == DisposeExceptionStrategy.AggregateAlways)
throw new AggregateException(mainException);
}
}
}
答案 7 :(得分:0)
这是一种方法,可以相当干净地抓取using
或Dispose
内容引发的任何异常。
原始代码:
using (var foo = new DisposableFoo())
{
codeInUsing();
}
然后这是代码,如果codeInUsing()
抛出或foo.Dispose()
抛出或两者抛出将抛出,并让你看到第一个异常(有时包装为InnerExeption,取决于):
var foo = new DisposableFoo();
Helpers.DoActionThenDisposePreservingActionException(
() =>
{
codeInUsing();
},
foo);
这不是很好但不是太糟糕。
以下是实现此目的的代码。我已将其设置为仅 按照未附加调试器时的描述工作,因为当附加调试器时,我更担心它会在第一个异常的正确位置中断。您可以根据需要进行修改。
public static void DoActionThenDisposePreservingActionException(Action action, IDisposable disposable)
{
bool exceptionThrown = true;
Exception exceptionWhenNoDebuggerAttached = null;
bool debuggerIsAttached = Debugger.IsAttached;
ConditionalCatch(
() =>
{
action();
exceptionThrown = false;
},
(e) =>
{
exceptionWhenNoDebuggerAttached = e;
throw new Exception("Catching exception from action(), see InnerException", exceptionWhenNoDebuggerAttached);
},
() =>
{
Exception disposeExceptionWhenExceptionAlreadyThrown = null;
ConditionalCatch(
() =>
{
disposable.Dispose();
},
(e) =>
{
disposeExceptionWhenExceptionAlreadyThrown = e;
throw new Exception("Caught exception in Dispose() while unwinding for exception from action(), see InnerException for action() exception",
exceptionWhenNoDebuggerAttached);
},
null,
exceptionThrown && !debuggerIsAttached);
},
!debuggerIsAttached);
}
public static void ConditionalCatch(Action tryAction, Action<Exception> conditionalCatchAction, Action finallyAction, bool doCatch)
{
if (!doCatch)
{
try
{
tryAction();
}
finally
{
if (finallyAction != null)
{
finallyAction();
}
}
}
else
{
try
{
tryAction();
}
catch (Exception e)
{
if (conditionalCatchAction != null)
{
conditionalCatchAction(e);
}
}
finally
{
if (finallyAction != null)
{
finallyAction();
}
}
}
}