[英]How to determine whether a .NET exception is being handled?
我們正在調查C#中的編碼模式,其中我們想要使用帶有特殊類的“using”子句,其Dispose()
方法根據“using”主體是正常退出還是異常退出而執行不同的操作。
據我所知,CLR會跟蹤當前正在處理的異常,直到它被“catch”處理程序占用為止。 但是,這些信息是否以任何方式公開以供代碼訪問並不完全清楚。 你知道它是否,如果是,如何訪問它?
例如:
using (var x = new MyObject())
{
x.DoSomething();
x.DoMoreThings();
}
class MyObject : IDisposable
{
public void Dispose()
{
if (ExceptionIsBeingHandled)
Rollback();
else
Commit();
}
}
這看起來幾乎像System.Transactions.TransactionScope
,除了成功/失敗不是通過調用x.Complete()
來確定,而是基於using
正文是否正常退出。
http://www.codewrecks.com/blog/index.php/2008/07/25/detecting-if-finally-block-is-executing-for-an-manhandled-exception/描述了一個“hack”來檢測是否您的代碼是否在異常處理模式下執行。 它使用Marshal.GetExceptionPointers來查看異常是否“活動”。
但請記住:
備注僅為編譯器支持結構化異常處理(SEH)公開GetExceptionPointers。 NoteNote:
此方法使用SecurityAction.LinkDemand來防止從不受信任的代碼調用它; 只有直接調用者才需要具有SecurityPermissionAttribute.UnmanagedCode權限。 如果可以從部分受信任的代碼調用代碼,則不要在沒有驗證的情況下將用戶輸入傳遞給Marshal類方法。 有關使用LinkDemand成員的重要限制,請參閱Demand vs. LinkDemand。
這個問題不是答案,而只是說明我從未在實際代碼中使用“接受”的黑客攻擊,所以它仍然在很大程度上未經測試“在野外”。 相反,我們去了這樣的事情:
DoThings(x =>
{
x.DoSomething();
x.DoMoreThings();
});
哪里
public void DoThings(Action<MyObject> action)
{
bool success = false;
try
{
action(new MyObject());
Commit();
success = true;
}
finally
{
if (!success)
Rollback();
}
}
關鍵點在於它與問題中的“使用”示例一樣緊湊,並且不使用任何黑客。
其中的缺點是性能損失(在我們的情況下完全可以忽略不計),而F10踩到DoThings
時我實際上希望它直接進入x.DoSomething()
。 兩者都很小。
您無法獲得此信息。
我會使用類似於DbTransaction類使用的模式:也就是說,你的IDisposable類應該實現一個與DbTransaction.Commit()類似的方法。 然后,您的Dispose方法可以執行不同的邏輯,具體取決於是否調用了Commit(在DbTransaction的情況下,如果事務沒有明確提交,則事務將被回滾)。
然后,您的類的用戶將使用以下模式,類似於典型的DbTransaction:
using(MyDisposableClass instance = ...)
{
... do whatever ...
instance.Commit();
} // Dispose logic depends on whether or not Commit was called.
編輯我看到你編輯了你的問題,以表明你已經知道這個模式(你的例子使用TransactionScope)。 不過,我認為這是唯一現實的解決方案。
using語句只是try finally塊的語法糖。 你可以通過最終完整地編寫try然后添加一個catch語句來處理你的特殊情況來得到你想要的東西:
try
{
IDisposable x = new MyThing();
}
catch (Exception exception) // Use a more specific exception if possible.
{
x.ErrorOccurred = true; // You could even pass a reference to the exception if you wish.
throw;
}
finally
{
x.Dispose();
}
在MyThing內部,如果需要,您可以執行此操作,例如:
class MyThing : IDisposable
{
public bool ErrorOccurred() { get; set; }
public void Dispose()
{
if (ErrorOccurred) {
RollBack();
} else {
Commit();
}
}
}
注意:我也想知道你為什么要這樣做。 它有一些代碼味道。 Dispose方法旨在清理非托管資源,而不是處理異常。 您可能最好在catch塊中編寫異常處理代碼,而不是在dispose中編寫,如果需要共享代碼,可以創建一些可以從兩個地方調用的有用輔助函數。
這是一個更好的方式來做你想要的:
using (IDisposable x = new MyThing())
{
x.Foo();
x.Bar();
x.CommitChanges();
}
class MyThing : IDisposable
{
public bool IsCommitted { get; private set; }
public void CommitChanges()
{
// Do stuff needed to commit.
IsCommitted = true;
}
public void Dispose()
{
if (!IsCommitted)
RollBack();
}
}
這似乎不是一個壞主意; 它在C#/ .NET中似乎並不理想。
在C ++中,有一個函數可以使代碼檢測是否由於異常而被調用。 這在RAII析構函數中最為重要; 根據控制流是正常還是異常,析構函數選擇提交或中止是一件微不足道的事。 我認為這是一種相當自然的方法,但缺乏內置支持(以及解決方法在道德上可疑的性質;它感覺相當依賴於實現)可能意味着應采取更傳統的方法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.