在IDisposable.Dispose
方法中有没有办法弄清楚是否抛出异常?
using (MyWrapper wrapper = new MyWrapper())
{
throw new Exception("Bad error.");
}
如果在using
语句中抛出异常,我希望在处理IDisposable
对象时知道它。
答案 0 :(得分:16)
您可以使用方法IDisposable
扩展Complete
并使用以下模式:
using (MyWrapper wrapper = new MyWrapper())
{
throw new Exception("Bad error.");
wrapper.Complete();
}
如果在using
语句Complete
中引发异常,则Dispose
之前不会调用AppDomain.CurrentDomain.FirstChanceException
。
如果您想知道抛出的确切异常,请订阅ThreadLocal<Exception>
事件并将最后抛出的异常存储在TransactionScope
变量中。
此类模式在{{1}}类中实现。
答案 1 :(得分:15)
否,在.Net框架中无法做到这一点,你无法弄清楚finally子句中正在抛出的当前异常。
请参阅此post on my blog,与Ruby中的类似模式进行比较,它突出了我认为存在于IDisposable模式中的差距。
Ayende有一个技巧可以让你detect an exception happened,但是,它不会告诉你它是哪个例外。
答案 2 :(得分:5)
不可能在Dispose()
方法中捕获异常。
但是,可以检查Dispose中的Marshal.GetExceptionCode()
以检测是否确实发生了异常,但我不会依赖它。
如果你不需要一个类并且只想捕获异常,你可以创建一个接受在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;
}
}
例如,您可以使用自动执行事务的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;
}
}
答案 3 :(得分:3)
詹姆斯,所有wrapper
能做的就是记录它自己的异常。您无法强制wrapper
的使用者记录他们自己的异常。这不是IDisposable的用途。 IDisposable用于对象的资源的半确定性释放。编写正确的IDisposable代码并非易事。
事实上,类的使用者甚至不需要调用你的类dispose方法,也不需要使用using块,所以它们都会崩溃。
如果从包装类的角度来看它,为什么它应该关心它是否存在于一个使用块中并且有一个例外?这会带来什么知识?让第三方代码知道异常细节和堆栈跟踪是否存在安全风险?如果计算中存在除零,wrapper
可以做什么?
记录异常的唯一方法是,无论IDisposable如何,都是try-catch,然后重新抛出catch。
try
{
// code that may cause exceptions.
}
catch( Exception ex )
{
LogExceptionSomewhere(ex);
throw;
}
finally
{
// CLR always tries to execute finally blocks
}
您提到您正在创建外部API。您必须使用try-catch在API的公共边界处包装每个调用,以便记录该异常来自您的代码。
如果你正在撰写公共API,那么你真的应该阅读Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries (Microsoft .NET Development Series) - 2nd Edition .. 1st Edition。
虽然我不提倡它们,但我已经看到IDisposable用于其他有趣的模式:
*这些模式可以通过另一层间接和匿名委托轻松实现,而无需重载IDisposable语义。重要的是,如果您或团队成员忘记正确使用它,您的IDisposable包装器将毫无用处。
答案 4 :(得分:2)
你可以这样做为“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");
}
}
答案 5 :(得分:1)
为什么不为此实现自己的逻辑,而不是using语句的语法糖。类似的东西:
try
{
MyWrapper wrapper = new MyWrapper();
}
catch (Exception e)
{
wrapper.CaughtException = true;
}
finally
{
if (wrapper != null)
{
wrapper.Dispose();
}
}
答案 6 :(得分:1)
不仅可以找出在处理一次性对象时是否抛出了异常,你甚至可以用一点魔法来触及在finally子句中抛出的异常。 ApiChange工具的My Tracing库使用此方法跟踪using语句中的异常。更多信息如何找到它here。
此致, Alois Kraus
答案 7 :(得分:1)
现在,2017年,这是执行此操作的通用方法,包括处理异常的回滚。
public static T WithinTransaction<T>(this IDbConnection cnn, Func<IDbTransaction, T> fn)
{
cnn.Open();
using (var transaction = cnn.BeginTransaction())
{
try
{
T res = fn(transaction);
transaction.Commit();
return res;
}
catch (Exception)
{
transaction.Rollback();
throw;
}
finally
{
cnn.Close();
}
}
}
你这样称呼它:
cnn.WithinTransaction(
transaction =>
{
var affected = ..sqlcalls..(cnn, ..., transaction);
return affected;
});
答案 8 :(得分:0)
这将捕获直接或在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()
}
但这依赖于他们使用这个代码 - 听起来你想让MyWrapper这样做。
using语句只是为了确保始终调用Dispose。它真的是这样做的:
MyWrapper wrapper;
try
{
wrapper = new MyWrapper();
}
finally {
if( wrapper != null )
wrapper.Dispose();
}
听起来你想要的是:
MyWrapper wrapper;
try
{
wrapper = new MyWrapper();
}
finally {
try{
if( wrapper != null )
wrapper.Dispose();
}
catch {
//only errors thrown by disposal
}
}
我建议在Dispose的实现中处理这个问题 - 无论如何你应该在Disposal中处理任何问题。
如果您正在占用某些资源,而您需要API的用户以某种方式释放它,请考虑使用Close()
方法。您的处置也应该调用它(如果它还没有),但是如果需要更好的控制,API的用户也可以自己调用它。
答案 9 :(得分:0)
如果你想完全保留在.net中,我建议的两种方法是编写一个“try-catch-finally”包装器,它将接受不同部分的委托,或者写一个“使用样式”的包装器,接受一个要调用的方法,以及一个或多个IDisposable对象,它们应在完成后处理。
“using-style”包装器可以处理try-catch块中的处理,如果在处理中抛出任何异常,则将它们包装在CleanupFailureException中,这将保留处理失败以及发生的任何异常。主委托,或者使用原始异常向异常的“Data”属性添加内容。我赞成在CleanupFailureException中包装东西,因为清理期间发生的异常通常表明比主线处理中出现的问题大得多;此外,可以编写一个CleanupFailureException以包含多个嵌套异常(如果有'n'个IDisposable对象,则可能有n + 1个嵌套异常:一个来自主线,一个来自每个Dispose)。
在vb.net中编写的“try-catch-finally”包装器,可以从C#调用,可以包含一些在C#中不可用的功能,包括将其扩展为“try-filter-catch-fault”的功能-finally“block”,在从异常中解除堆栈之前执行“filter”代码并确定是否应该捕获异常,“fault”块将包含仅在发生异常时才运行的代码,但是实际上并没有捕获它,“fault”和“finally”块都会收到参数,指示在执行“try”期间发生了什么异常(如果有的话),以及“try”是否成功完成(注意,顺便说一句,即使主线完成,异常参数也可能是非空的;纯C#代码无法检测到这种情况,但vb.net包装器可以)。
答案 10 :(得分:0)
就我而言,我想在微服务崩溃时进行记录。我已经有一个using
在实例关闭之前正确清理,但如果由于异常我想知道为什么,我讨厌没有答案。
不是试图让它在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))
}
}
实施:
using (var wrapper = new MyWrapper())
{
wrapper.Start((string1, string2, string3) =>
{
Console.WriteLine(string1);
Console.WriteLine(string2);
Console.WriteLine(string3);
}
}
根据您的需要,这可能过于严格,但它可以满足我的需求。