C# 在 IDisposable.Dispose 中拦截异常
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/220234/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Intercepting an exception inside IDisposable.Dispose
提问by James Newton-King
In the IDisposable.Dispose
method is there a way to figure out if an exception is being thrown?
在IDisposable.Dispose
方法中有没有办法确定是否抛出异常?
using (MyWrapper wrapper = new MyWrapper())
{
throw new Exception("Bad error.");
}
If an exception is thrown in the using
statement I want to know about it when the IDisposable
object is disposed.
如果在using
语句中抛出异常,我想在处理IDisposable
对象时了解它。
采纳答案by Sam Saffron
No, there is no way to do this in the .Net framework, you cannot figure out the current-exception-which-is-being-thrown in a finally clause.
不,在 .Net 框架中无法做到这一点,您无法弄清楚 finally 子句中抛出的当前异常。
See this post on my blog, for a comparison with a similar pattern in Ruby, it highlights the gaps I think exist with the IDisposable pattern.
请参阅我博客上的这篇文章,与 Ruby 中的类似模式进行比较,它突出了我认为与 IDisposable 模式存在的差距。
Ayende has a trick that will allow you to detect an exception happened, however, it will not tell you which exception it was.
Ayende 有一个技巧,可以让您检测发生的异常,但是,它不会告诉您它是哪个异常。
回答by tvanfosson
Instead of the syntactic sugar of the using statement, why not just implement your own logic for this. Something like:
与其使用 using 语句的语法糖,为什么不为此实现您自己的逻辑。就像是:
try
{
MyWrapper wrapper = new MyWrapper();
}
catch (Exception e)
{
wrapper.CaughtException = true;
}
finally
{
if (wrapper != null)
{
wrapper.Dispose();
}
}
回答by Robert Paulson
James, All wrapper
can do is log it's own exceptions. You can't force the consumer of wrapper
to log their own exceptions. That's not what IDisposable is for. IDisposable is meant for semi-deterministic release of resources for an object. Writing correct IDisposable code is not trivial.
詹姆斯,wrapper
所能做的就是记录它自己的异常。你不能强迫消费者wrapper
记录他们自己的异常。这不是 IDisposable 的用途。IDisposable 用于半确定性地释放对象的资源。编写正确的 IDisposable 代码并非易事。
In fact, the consumer of the class isn't even required to call your classes dispose method, nor are they required to use a using block, so it all rather breaks down.
事实上,类的使用者甚至不需要调用你的类的 dispose 方法,也不需要他们使用 using 块,所以这一切都会崩溃。
If you look at it from the point of view of the wrapper class, why should it care that it was present inside a using block and there was an exception? What knowledge does that bring? Is it a security risk to have 3rd party code privy to exception details and stack trace? What can wrapper
do if there is a divide-by-zero in a calculation?
如果你从包装类的角度来看它,它为什么要关心它是否存在于 using 块中并且存在异常?这带来什么知识?让第 3 方代码了解异常详细信息和堆栈跟踪是否存在安全风险?有什么可以wrapper
做,如果有一个计算除以零?
The only way to log exceptions, irrespective of IDisposable, is try-catch and then to re-throw in the catch.
无论 IDisposable 是什么,记录异常的唯一方法是 try-catch,然后重新抛出 catch。
try
{
// code that may cause exceptions.
}
catch( Exception ex )
{
LogExceptionSomewhere(ex);
throw;
}
finally
{
// CLR always tries to execute finally blocks
}
You mention you're creating an external API. You would have to wrap every call at your API's public boundary with try-catch in order to log that the exception came from your code.
您提到您正在创建一个外部 API。您必须使用 try-catch 包装 API 公共边界处的每个调用,以便记录异常来自您的代码。
If you're writing a public API then you really ought to read Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries (Microsoft .NET Development Series) - 2nd Edition.. 1st Edition.
如果您正在编写公共 API,那么您真的应该阅读Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries (Microsoft .NET Development Series) - 2nd Edition.. 1st Edition。
While I don't advocate them, I have seen IDisposable used for other interesting patterns:
虽然我不提倡它们,但我已经看到 IDisposable 用于其他有趣的模式:
- Auto-rollback transaction semantics. The transaction class would rollback the transaction on Dispose if not already committed.
- Timed code blocks for logging. During object creation a timestamp was recorded, and on Dispose the TimeSpan was calculated and a log event was written.
- 自动回滚事务语义。如果尚未提交,事务类将在 Dispose 上回滚事务。
- 用于记录的定时代码块。在对象创建过程中,会记录一个时间戳,并在 Dispose 时计算 TimeSpan 并写入日志事件。
* These patterns can be achieved with another layer of indirection and anonymous delegates easily and without having to overload IDisposable semantics. The important note is that your IDisposable wrapper is useless if you or a team member forget to use it properly.
* 这些模式可以通过另一层间接和匿名委托轻松实现,而无需重载 IDisposable 语义。重要的一点是,如果您或团队成员忘记正确使用它,那么您的 IDisposable 包装器将毫无用处。
回答by Keith
This will catch exceptions thrown either directly or inside the dispose method:
这将捕获直接或在 dispose 方法中抛出的异常:
try
{
using (MyWrapper wrapper = new MyWrapper())
{
throw new MyException("Bad error.");
}
}
catch ( MyException myex ) {
//deal with your exception
}
catch ( Exception ex ) {
//any other exception thrown by either
//MyWrapper..ctor() or MyWrapper.Dispose()
}
But this is relying on them using this this code - it sounds like you want MyWrapper to do that instead.
但这依赖于他们使用此代码 - 听起来您希望 MyWrapper 这样做。
The using statement is just to make sure that Dispose always gets called. It's really doing this:
using 语句只是为了确保始终调用 Dispose。它真的是这样做的:
MyWrapper wrapper;
try
{
wrapper = new MyWrapper();
}
finally {
if( wrapper != null )
wrapper.Dispose();
}
It sounds like what you want is:
听起来你想要的是:
MyWrapper wrapper;
try
{
wrapper = new MyWrapper();
}
finally {
try{
if( wrapper != null )
wrapper.Dispose();
}
catch {
//only errors thrown by disposal
}
}
I would suggest dealing with this in your implementation of Dispose - you should handle any issues during Disposal anyway.
我建议在你的 Dispose 实现中处理这个问题——无论如何你应该在 Disposal 期间处理任何问题。
If you're tying up some resource where you need users of your API to free it in some way consider having a Close()
method. Your dispose should call it too (if it hasn't been already) but users of your API could also call it themselves if they needed finer control.
如果您在需要 API 用户以某种方式释放它的地方占用了一些资源,请考虑使用一种Close()
方法。您的处置也应调用它(如果尚未调用),但您的 API 用户如果需要更好的控制,也可以自己调用它。
回答by Alois Kraus
It is not only possible to find out if an exception was thrown when a disposable object is disposed you can even get your hands on the exception that was thrown inside the finally clause with a little magic. My Tracing library of the ApiChange tool employs this method to trace exceptions inside a using statement. More infos how this works can be found here.
不仅可以找出在处置一次性对象时是否抛出异常,您甚至可以使用一点魔法来了解在 finally 子句中抛出的异常。我的 ApiChange 工具的跟踪库使用此方法来跟踪 using 语句中的异常。可以在此处找到有关其工作原理的更多信息。
Yours, Alois Kraus
你的,阿洛伊斯·克劳斯
回答by supercat
If you want to stay purely within .net, two approaches I would suggest would be writing a "try-catch-finally" wrapper, which would accept delegates for the different parts, or else writing a "using-style" wrapper, which accept a method to be invoked, along with one or more IDisposable objects which should be disposed after it completes.
如果你想完全留在 .net 中,我建议的两种方法是编写一个“try-catch-finally”包装器,它接受不同部分的委托,或者编写一个“using-style”包装器,它接受一种要调用的方法,以及一个或多个 IDisposable 对象,这些对象在它完成后应该被释放。
A "using-style" wrapper could handle the disposal in a try-catch block and, if any exceptions are thrown in disposal, either wrap them in a CleanupFailureException which would hold the disposal failures as well as any exception that occurred in the main delegate, or else add something to the exception's "Data" property with the original exception. I would favor wrapping things in a CleanupFailureException, since an exception that occurs during cleanup will generally indicate a much bigger problem than one which occurs in main-line processing; further, a CleanupFailureException could be written to include multiple nested exceptions (if there are 'n' IDisposable objects, there could be n+1 nested exceptions: one from the mainline and one from each Dispose).
“使用风格”包装器可以在 try-catch 块中处理处理,并且如果在处理中抛出任何异常,则将它们包装在 CleanupFailureException 中,它将保存处理失败以及发生在主委托中的任何异常,或者在原始异常的“数据”属性中添加一些内容。我倾向于将事情包装在 CleanupFailureException 中,因为在清理期间发生的异常通常表明比在主线处理中发生的问题大得多的问题;此外,可以编写 CleanupFailureException 以包含多个嵌套异常(如果有“n”个 IDisposable 对象,则可能有 n+1 个嵌套异常:一个来自主线,一个来自每个 Dispose)。
A "try-catch-finally" wrapper written in vb.net, while callable from C#, could include some features that are otherwise unavailable in C#, including the ability to expand it to a "try-filter-catch-fault-finally" block, where the "filter" code would be executed before the stack is unwound from an exception and determine whether the exception should be caught, the "fault" block would contain code that would only run if an exception occurred, but would not actually catch it, and both the "fault" and "finally" blocks would receive parameters indicating both what exception (if any) occurred during the execution of the "try", and whether the "try" completed successfully (note, btw, that it would be possible for the exception parameter to be non-null even if the mainline completes; pure C# code couldn't detect such a condition, but the vb.net wrapper could).
即使主线完成,异常参数也可能为非空;纯 C# 代码无法检测到这种情况,但 vb.net 包装器可以)。
回答by sammyboy
You can do this buy implementing the Dispose method for the "MyWrapper" class. In the dispose method you can check to see if there is an exception as follows
您可以通过为“MyWrapper”类实现 Dispose 方法来执行此操作。在dispose方法中可以查看是否有异常如下
public void Dispose()
{
bool ExceptionOccurred = Marshal.GetExceptionPointers() != IntPtr.Zero
|| Marshal.GetExceptionCode() != 0;
if(ExceptionOccurred)
{
System.Diagnostics.Debug.WriteLine("We had an exception");
}
}
回答by Jo VdB
It is not possibleto capture the Exception in the Dispose()
method.
这是不可能捕获的异常Dispose()
的方法。
However, it is possible to check Marshal.GetExceptionCode()
in the Dispose to detect if an Exception did occur, but I wouldn't rely on that.
但是,可以检查Marshal.GetExceptionCode()
Dispose 以检测是否确实发生了异常,但我不会依赖它。
If you don't need a class and want just want to capture the Exception, you can create a function that accepts a lambda that is executed within a try/catch block, something like this:
如果您不需要类并且只想捕获异常,您可以创建一个函数来接受在 try/catch 块中执行的 lambda,如下所示:
HandleException(() => {
throw new Exception("Bad error.");
});
public static void HandleException(Action code)
{
try
{
if (code != null)
code.Invoke();
}
catch
{
Console.WriteLine("Error handling");
throw;
}
}
As example, You can use a method that automatically does a Commit() or Rollback() of a transaction and do some logging. At this way you don't always need a try/catch block.
例如,您可以使用自动执行事务的 Commit() 或 Rollback() 并执行一些日志记录的方法。通过这种方式,您并不总是需要 try/catch 块。
public static int? GetFerrariId()
{
using (var connection = new SqlConnection("..."))
{
connection.Open();
using (var transaction = connection.BeginTransaction())
{
return HandleTranaction(transaction, () =>
{
using (var command = connection.CreateCommand())
{
command.Transaction = transaction;
command.CommandText = "SELECT CarID FROM Cars WHERE Brand = 'Ferrari'";
return (int?)command.ExecuteScalar();
}
});
}
}
}
public static T HandleTranaction<T>(IDbTransaction transaction, Func<T> code)
{
try
{
var result = code != null ? code.Invoke() : default(T);
transaction.Commit();
return result;
}
catch
{
transaction.Rollback();
throw;
}
}
回答by Kelqualyn
You can extend IDisposable
with method Complete
and use pattern like that:
您可以IDisposable
使用方法扩展Complete
并使用这样的模式:
using (MyWrapper wrapper = new MyWrapper())
{
throw new Exception("Bad error.");
wrapper.Complete();
}
If an exception is thrown inside the using
statement Complete
will not be called before Dispose
.
如果内部抛出异常,则之前不会调用该using
语句。Complete
Dispose
If you want to know what exact exception is thrown, then subscribe on AppDomain.CurrentDomain.FirstChanceException
event and store last thrown exception in ThreadLocal<Exception>
variable.
如果您想知道抛出的确切异常,则订阅AppDomain.CurrentDomain.FirstChanceException
事件并将最后抛出的异常存储在ThreadLocal<Exception>
变量中。
Such pattern implemented in TransactionScope
class.
这种模式在TransactionScope
课堂上实现。
回答by Chad Hedgcock
In my case, I wanted to do this to log when an microservice crashes. I already have in place a using
to properly clean up right before an instance shut down, but if that's because of an exception I want to see why, and I hate no for an answer.
就我而言,我想这样做以在微服务崩溃时记录日志。我已经using
准备好在实例关闭之前正确清理,但如果这是因为异常,我想知道原因,我讨厌没有答案。
Instead of trying to make it work in Dispose()
, perhaps make a delegate for the work you need to do, and then wrap your exception-capturing in there. So in my MyWrapper logger, I add a method that takes an Action / Func:
与其试图让它在 中工作Dispose()
,不如为您需要做的工作指定一个委托,然后将您的异常捕获包装在那里。所以在我的 MyWrapper 记录器中,我添加了一个接受 Action / Func 的方法:
public void Start(Action<string, string, string> behavior)
try{
var string1 = "my queue message";
var string2 = "some string message";
var string3 = "some other string yet;"
behaviour(string1, string2, string3);
}
catch(Exception e){
Console.WriteLine(string.Format("Oops: {0}", e.Message))
}
}
To implement:
实施:
using (var wrapper = new MyWrapper())
{
wrapper.Start((string1, string2, string3) =>
{
Console.WriteLine(string1);
Console.WriteLine(string2);
Console.WriteLine(string3);
}
}
Depending on what you need to do, this may be too restrictive, but it worked for what I needed.
根据您需要做什么,这可能过于严格,但它适用于我所需要的。