簡體   English   中英

如何確定是否正在處理.NET異常?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM