2011-03-07 19 views
6

我在今天工作的代碼庫中遇到了一個模式,最初看起來非常聰明,後來讓我瘋了,現在我想知道是否有辦法在最小化瘋狂的同時拯救聰明的部分。使用IDisposable檢查約束 - 瘋狂還是天才?

我們有一堆的實施IContractObject對象,並且看起來像這樣一類InvariantChecker

internal class InvariantChecker : IDisposable 
{ 
    private IContractObject obj; 

    public InvariantChecker(IContractObject obj) 
    { 
     this.obj = obj; 
    } 

    public void Dispose() 
    { 
     if (!obj.CheckInvariants()) 
     { 
      throw new ContractViolatedException(); 
     } 
    } 
} 

internal class Foo : IContractObject 
{ 
    private int DoWork() 
    { 
     using (new InvariantChecker(this)) 
     { 
      // do some stuff 
     } 
     // when the Dispose() method is called here, we'll throw if the work we 
     // did invalidated our state somehow 
    } 
} 

這是用來提供狀態的一致性相對容易的運行時間驗證。我沒有寫這個,但它最初似乎是一個非常酷的想法。

但是,如果Foo.DoWork引發異常,則會出現問題。拋出異常時,很可能我們處於不一致狀態,這意味着也會拋出隱藏原始異常的情況。這種情況可能會發生多次,因爲異常會在調用堆棧中傳播,每幀中的InvariantChecker會隱藏下面幀中的異常。爲了診斷問題,我必須禁用InvariantChecker中的拋出,然後才能看到原始異常。

這顯然很糟糕。然而,有沒有什麼辦法可以挽救原創想法的聰明,而不會產生可怕的異常隱藏行爲

+6

這是可怕的。 – jason 2011-03-07 16:46:53

+0

是不是有一條規則,即析構函數應該盡其所能避免拋出異常? – driushkin 2011-03-07 17:11:52

+0

這是IDisposable濫用後果的一個很好的例子。和CA1065,二合一。 – 2011-03-07 17:48:48

回答

0

將屬性添加到InvariantChecker類中,該類允許您抑制檢查/投擲。

internal class InvariantChecker : IDisposable 
{ 
    private IContractObject obj; 

    public InvariantChecker(IContractObject obj) 
    { 
     this.obj = obj; 
    } 

    public bool Suppress { get; set; } 

    public void Dispose() 
    { 
     if (!this.Suppress) 
     { 
      if (!obj.CheckInvariants()) 
      { 
       throw new ContractViolatedException(); 
      } 
     } 
    } 
} 

internal class Foo : IContractObject 
{ 
    private int DoWork() 
    { 
     using (var checker = new InvariantChecker(this)) 
     { 
      try 
      { 
       // do some stuff 
      } 
      catch 
      { 
       checker.Suppress = true; 
       throw; 
      } 
     } 
    } 
} 
+2

這是在可怕的頂部的可怕。 – jason 2011-03-07 16:50:39

+0

我仍然不同意,它是一樣糟糕,因爲你認爲它是,但我同意,喬恩的模式是更清潔,因爲這是爲了拋出一個例外,而不是實際「清理」的東西。 – Toby 2011-03-07 17:00:52

5

這是對使用模式的可怕濫用。使用模式是用於處理非託管資源,而不是像這樣的「聰明」技巧。我建議你直接寫代碼。

+0

實際上,這是我稱之爲「範圍」模式的細微變化,在正確使用時非常有用。就像框架的Transactions命名空間中的TransactionScope對象一樣。 – Toby 2011-03-07 16:53:44

+0

我認爲使用模式可以用於「聰明」的技巧,儘管這不是一個很好的技巧。我喜歡ASP.Net MVC團隊用它來開始/結束FORMS的方式,儘管 – 2011-03-07 16:55:17

+0

我也在其他地方看到過。例如,SharePoint有一個'SPMonitoredScope'對象,用於將執行信息寫入使用此模式的日誌。 – 2011-03-07 16:56:33

9

我不喜歡以這種方式重載using的含義的想法。爲什麼不用一個靜態方法來代替委託類型呢?所以,你會寫:

InvariantChecker.Check(this,() => 
{ 
    // do some stuff 
}); 

甚至更​​好,只是使它的擴展方法:

this.CheckInvariantActions(() => 
{ 
    // do some stuff 
}); 

(請注意,「這個」部分,需要爲了得到C#編譯器看對於適用於this的擴展方法。)這也允許您使用「普通」方法來實現該操作(如果需要),並使用方法組轉換爲其創建委託。如果您有時想從身體返回,您可能還想讓它返回一個值。

現在CheckInvariantActions可以使用類似:

action(); 
if (!target.CheckInvariants()) 
{ 
    throw new ContractViolatedException(); 
} 

我也建議CheckInvariants也許應該直接拋出異常,而不是僅僅返回bool - 這樣的異常可以給有關不變的是侵犯。

+0

+1。此外,我認爲「使用」更容易調試(代表生成非顯而易見的堆棧,可以跳入和跳出相同的功能,對於在LINQ之前出生的人也可能很陌生),「使用」版本會生成更少的代碼你得到委託開銷,你仍然需要try/finally在CheckInvariantActions裏面)。 – 2011-03-07 17:21:42

+0

@Alexei:不,你*不需要嘗試/最後 - 如果操作拋出異常,OP *不會執行檢查。 – 2011-03-07 18:59:59

+0

我認爲OP的帖子是「我對合同驗證感到滿意,但我想知道什麼是失敗的」,您的閱讀「我喜歡酷代碼,需要合同驗證僅適用於成功案例」,接近原始含義。 – 2011-03-07 19:23:26

2

如果你真的要做到這一點:

internal class InvariantChecker : IDisposable 
{ 
    private IContractObject obj; 

    public InvariantChecker(IContractObject obj) 
    { 
     this.obj = obj; 
    } 

    public void Dispose() 
    { 
     if (Marshal.GetExceptionCode() != 0xCCCCCCCC && obj.CheckInvariants()) 
     { 
      throw new ContractViolatedException(); 
     } 
    } 
} 
+0

如果不是在存在未決異常時僅僅抑制ContractViolatedException,而是將待定異常包裝在ContractViolatedException中,那看起來會更好。你能證明嗎? – supercat 2011-03-07 17:03:19

+0

@supercat:這是不平凡的,作爲讀者的練習:) – 2011-03-07 17:07:56

0

如果你目前的問題是讓原始異常 - 去調試 - >異常,並檢查「拋出」對所有CLR例外。當拋出異常時它會中斷,因此你會首先看到它。如果異常從VS的角度來看「不是你的代碼」,則可能還需要關閉tools-> options-> debug - >「僅限我的代碼」選項。

0

使這個更好的是需要一個乾淨的方法來找出Dispose被調用時是否有異常。微軟應該提供一種標準化的方法來隨時查明在當前try-finally模塊退出時哪些異常(如果有)將被掛起,或者微軟應該支持擴展的Dispose接口(可能會繼承Dispose的DisposeEx)接受一個pending-exception參數。

1

取而代之的是:

using (new InvariantChecker(this)) { 
    // do some stuff 
} 

只是這樣做(假設你不從do some stuff返程):

// do some stuff 
this.EnforceInvariants(); 

如果您需要從do some stuff回來,我相信有些refactoring是爲了:

DoSomeStuff(); // returns void 
this.EnforceInvariants(); 

... 

var result = DoSomeStuff(); // returns non-void 
this.EnforceInvariants(); 
return result; 

它更簡單,你不會遇到你以前遇到的問題。

你只需要一個簡單的擴展方法:

public static class InvariantEnforcer { 
    public static void EnforceInvariants(this IContractObject obj) { 
    if (!obj.CheckInvariants()) { 
     throw new ContractViolatedException(); 
    } 
    } 
} 
+0

這與John Skeet的解決方案基本相同。 – 2011-03-07 18:02:16

+0

@JSBangs:不,它不是。他的解決方案使用代表。 – 2011-03-07 18:05:26