IDisposable.Dispose
方法中是否有一种方法可以确定是否抛出异常?
using (MyWrapper wrapper = new MyWrapper())
{
throw new Exception("Bad error.");
}
如果在using
语句中抛出异常,我希望在IDisposable
对象被处理时得到通知。
IDisposable.Dispose
方法中是否有一种方法可以确定是否抛出异常?
using (MyWrapper wrapper = new MyWrapper())
{
throw new Exception("Bad error.");
}
如果在using
语句中抛出异常,我希望在IDisposable
对象被处理时得到通知。
Complete
方法扩展 IDisposable
,并使用类似以下的模式:using (MyWrapper wrapper = new MyWrapper())
{
throw new Exception("Bad error.");
wrapper.Complete();
}
using
语句内部抛出异常,则在Dispose
之前不会调用Complete
函数。AppDomain.CurrentDomain.FirstChanceException
事件,并将最后一个抛出的异常存储在ThreadLocal<Exception>
变量中。TransactionScope
类中。Marshal.GetExceptionCode()
的未来并不光明:在未来的版本中,GetExceptionCode() 可能无法使用。Marshal.GetExceptionCode Method - t3chb0t在Dispose()
方法中无法捕获异常。
但是,在Dispose中可以检查Marshal.GetExceptionCode()
,以检测是否发生了异常,但我不会依赖它。
如果您不需要一个类,只想捕获异常,可以创建一个接受lambda表达式的函数,并在try/catch块中执行该表达式,类似于以下内容:
HandleException(() => {
throw new Exception("Bad error.");
});
public static void HandleException(Action code)
{
try
{
if (code != null)
code.Invoke();
}
catch
{
Console.WriteLine("Error handling");
throw;
}
}
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;
}
}
Dispose
被定义为接受一个类型为Exception
的参数,该参数将指示在try/finally上下文(如果有)保护对象时是否有任何异常待处理。询问线程是否有待处理的异常并不完全相同,因为Dispose
本身可能会在嵌套在保护要处理的对象的try/finally块内的try/finally块中调用。 - supercatwrapper
只能记录自己的异常。你无法强制使用wrapper
的消费者记录他们自己的异常。这不是IDisposable的用途。IDisposable用于半确定性释放对象资源。编写正确的IDisposable代码并不容易。wrapper
可以做些什么?try
{
// code that may cause exceptions.
}
catch( Exception ex )
{
LogExceptionSomewhere(ex);
throw;
}
finally
{
// CLR always tries to execute finally blocks
}
您提到您正在创建一个外部API。为了记录异常来自您的代码,您需要在API的公共边界处包装每个调用以进行try-catch。
如果您正在编写公共API,则真的应该阅读Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries (Microsoft .NET Development Series) - 2nd Edition..1st Edition。
虽然我不赞成它们,但我已经看到IDisposable用于其他有趣的模式:
*这些模式可以轻松地使用另一层间接和匿名委托实现,而无需重载IDisposable语义。重要的是,如果您或团队成员忘记正确使用它,则您的IDisposable包装器将无用。
using
块而不调用它应该会触发一个异常,除非你离开块是因为抛出了异常,在这种情况下通过抛出另一个异常来销毁第一个异常将是非常粗鲁的。 - supercatpublic void Dispose()
{
bool ExceptionOccurred = Marshal.GetExceptionPointers() != IntPtr.Zero
|| Marshal.GetExceptionCode() != 0;
if(ExceptionOccurred)
{
System.Diagnostics.Debug.WriteLine("We had an exception");
}
}
在我的情况下,我想要记录微服务崩溃的情况。在实例关闭之前,我已经使用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);
}
}
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;
});
不妨尝试自己实现逻辑,而非使用using语句的语法糖。可以像这样:
try
{
MyWrapper wrapper = new MyWrapper();
}
catch (Exception e)
{
wrapper.CaughtException = true;
}
finally
{
if (wrapper != null)
{
wrapper.Dispose();
}
}
这将捕获直接抛出或在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
}
}
Close()
方法。如果尚未调用,则您的dispose也应该调用它,但是如果需要更精细的控制,API用户也可以自行调用它。如果您想保持纯粹的.NET环境,我会建议两种方法:一是编写一个"try-catch-finally"封装器,该封装器将接受不同部分的委托,或者编写一个“using-style”封装器,该封装器需要一个要调用的方法以及一个或多个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”块,其中“filter”代码将在堆栈从异常中解除之前执行,并确定是否应该捕获异常,“fault”块将包含仅在发生异常时才运行但实际上不会捕获异常的代码,而且“fault”和“finally”块都将接收参数,指示“try”执行期间发生了什么异常(如果有),以及“try”是否成功完成(顺便说一下,即使主线程完成,异常参数也可能是非空的;纯C#代码无法检测到这种情况,但vb.net包装器可以)。