本文章是由機器翻譯。

技術最前線

Visual Studio 2010 中的程式碼合約設定

Dino Esposito

上個月我介紹軟體合約,它們是在 Microsoft 中實作。NET Framework 4。稱為程式碼合約,[軟體合約會讓您以表達您的程式碼應該滿足能夠正常運作的型式條件。程式碼合約會造成您的程式碼擲回例外狀況,如果方法不會接收資料,如預期般運作,或如果它會產生不遵循預期達成條件來形容的資料。先決條件和達成條件來形容概觀,您可能想要簽出我的文件,在上個月的問題 (msdn.microsoft.com/magazine/gg983479)。

程式碼合約是部份。NET Framework 4,但也依賴 Visual Studio 2010,例如執行階段工具的幾個設備整合 MSBuild 以及在 [專案屬性] 方塊中的屬性頁。請務必注意只需撰寫先決條件和達成條件來形容是不夠的。您也需要執行階段檢查喜歡軟體合約的每個專案的功能已開啟。透過 Visual Studio 2010 程式碼合約專案屬性頁執行該動作。

在本文中我將討論的各種選項,您可以檢查或選取並向下切入至產品工具和處理程式碼合約最常見的作業的作法的預定的用途:引數的驗證。

程式碼合約屬性頁

應該強制程式碼合約先決條件和達成條件來形容執行中的所有組建,或只在偵錯組建嗎?實際上這往往取決於您 conception 軟體合約。它是設計工作的一部份嗎?還是只是偵錯的量值吗?

如果它是設計函式,然後有就沒有必要在發行的組建中去除掉合約。如果它只是偵錯技巧,您不想讓它解決當您編譯發行] 模式中。

在中。NET Framework、 程式碼合約的只是其中一部分的架構,以及會納入任何語言。如此一來,很容易將其設定為基礎建置每個專案中。。NET Framework 實作軟體合約因此輸入您要決定何處及何時適合實作合約。

圖 1在 Visual Studio 2010 透過它您設定要如何運作的軟體合約應用程式中會顯示屬性頁。請注意這類設定套用每個專案為基礎,因此可以適當地調整。

圖 1在 Visual Studio 2010 協定的程式碼的 [屬性] 頁面

您可以選取選擇 (偵錯,發行等等) 的組態和設定套用至該設定。如此一來,您可以啟用程式碼合約,以進行偵錯,但並不適合發行以及更重要的是,您可以反轉隨時決策。

執行階段檢查

若要啟用程式碼合約您必須檢查執行執行階段合約檢查] 選項。如果您將該未核取,然後周圍的原始程式碼,您可能必須任何合約指示會產生任何效果 (除了 Contract.Assert 和 Contract.Assume 在任何組建位置偵錯符號定義,但這是有點小點)。核取方塊會控制是否在每個編譯步驟結尾處,就會觸發 「 產品 」 工具。已經是 post-processes 軟體的外部工具協定並修改之 MSIL 程式碼放置在正確的位置指定條件、 postcondition 和恆定性檢查。

如果您保留已經,但請注意如果您有如下的指定條件,則要取得執行階段判斷提示失敗:

Contract.Requires<TException>(condition)

圖 2顯示訊息方塊,您取得。

圖 2程式碼要求執行階段合約檢查

若要查看如何執行階段檢查運作,詳細資料,請考慮下列程式碼:

public Int32 Sum(Int32 x, Int32 y) {
  // Check input values
  ValidateOperands(x, y);
  ValidateResult();

  // Perform the operation
  if (x == y)
    return 2 * x; 
  return x + y;
}

合約詳細資料會儲存在使用 ContractAbbreviators 在上個月的專欄中所述的 ValidateXxx 方法。 以下是 ValidateXxx 方法的程式碼:

[ContractAbbreviator]
private void ValidateOperands(Int32 x, Int32 y) {
  Contract.Requires(x >= 0 && y >= 0);
}

[ContractAbbreviator]
private void ValidateResult() {
  Contract.Ensures(Contract.Result<Int32>() >= 0);
}

如果您使用 Contract.Requires,而不是 Contract.Requires <TException>,然後儲存您自己從失敗的圖 2如果您將已經放在其中一個組建。 訊息方塊中圖 2是的 Contract.Requires,看起來就像這樣的內部實作受限於:

[Conditional("CONTRACTS_FULL")]
public static void Requires(bool condition, string userMessage) {
  AssertMustUseRewriter(
    ContractFailureKind.Precondition, "Requires");
}

public static void Requires<TException>(bool condition) 
  where TException: Exception {
  AssertMustUseRewriter(
    ContractFailureKind.Precondition, "Requires<TException>");
}

條件式屬性指出編譯器除非 CONTRACTS_FULL 符號定義,應該忽略這類方法呼叫。只有當您開啟執行執行階段合約檢查] 選項時,被定義這個符號。因為 Contract.Requires <TException> 不條件式地定義,缺少這個屬性,就會執行已經核取,而且如果停用執行階段合約檢查會導致失敗的判斷提示。

讓我們往前移動,並考慮在前述的程式碼上使用產品的效果。您很容易地可以確認我說只要使用中斷點並擊中 Ctrl + F11 以顯示 [反組譯碼] 檢視中 Visual Studio 2010 為您自己。圖 3時您逐步執行加總編譯時沒有啟用執行階段合約檢查這個方法會顯示 [反組譯碼] 檢視的內容。如您所見,原始程式碼就如您所撰寫的類別中。

圖 3未檢查的執行階段合約的反組譯碼檢視

當您啟用執行階段檢查時,「 產品 」 工具經過編譯器傳回,並編輯 MSIL 程式碼。如果您逐步執行相同的程式碼與啟用的程式碼合約時,您會看到類似圖 4

圖 4達成條件來形容檢查過去的 Return 陳述式

顯著的差異是結束 Sum 方法前,向右及過去的 return 陳述式叫用 ValidateResult。您不一定要讀取中的程式碼中運作 MSIL 大師圖 4。此方法啟動承認先決條件的最上層位置之前,必須經過驗證運算元。包含達成條件來形容的程式碼會移到底部的方法,而最後的 return 陳述式的 MSIL 程式碼只是屬於它。更有趣的是,第一個傳回陳述式 — 實作捷徑的 Sum 方法中的一個,現在只要跳至位址 ValidateResult 開始的位置:

...
傳回 2 * x;
00000054 有 mov eax,dword ptr [ebp-8]
00000057 新增 eax,eax
00000059 有 mov dword ptr [ebp 0Ch]、 eax
0000005c nop
0000005d jmp 0000006B
...
ValidateResult()
0000006b 發送 dword ptr ds: [02C32098h]
...

回到圖 1,請注意附近執行執行階段合約檢查] 核取方塊的下拉式清單。該清單可讓您指出您想要啟用軟體合約的數目。有各種層級:完整、 Pre 和 Post、 先決條件、 ReleaseRequires 及無。

完整表示支援所有類型的軟體合約,並且 [無] 表示其中沒有列入考量。Pre 和 Post 排除恆定性。先決條件也不包括確認陳述式。

ReleaseRequires 不支援 Contract.Requires 方法,只允許您指定使用的先決條件需要 <TException> 或舊版的 If 然後 Throw 說明檔。

專案屬性頁可讓您啟用或停用執行階段依據各個專案,但是如果您想要停用檢查只在您的程式碼的幾個區段上的執行階段檢查吗?在這種情況下您就可以停用執行階段檢查以程式設計方式使用 ContractRuntimeIgnored 屬性。然而,較新版本 (1.4.40307.0) 加入新的略過數量詞選項,也可讓您不會執行任何包含參照 Contract.ForAll 或 Contract.Exists 的合約。

您可以將屬性套用到成員也運算式中使用合約。如果成員以屬性裝飾然後所在的整個合約陳述式不會執行階段檢查。屬性無法辨識這類的判斷提示和假設的合約方法中。

組件模式

程式碼合約屬性也可讓您設定合約組件模式] 設定。設定指的是您想要執行的引數驗證的方式。有兩種方式可以:標準合約需要和合約參考組件。組件模式] 設定可協助調整程式碼,並提供適當的警告,必要時已經等工具。讓我們假設您使用組件模式來宣告的參數驗證程式碼合約的用途。組件模式設定介紹幾個簡單的規則,都必須符合,否則就會發生編譯錯誤。

組件模式應該是設定為標準合約需要如果您使用的方法需要而需要 <T> 若要驗證方法的引數。如果您使用任何 If 然後 Throw 陳述式作為先決條件,您應該使用自訂參數驗證。陳述式如果您不使用自訂參數驗證,請將它視為需要 <T>。自訂參數驗證和明確使用任何形式的組合需要陳述式會,相反地,擲回編譯器錯誤。

使用之間的差異為何需要以及使用 If 然後 Throw 陳述式嗎?一定的 If 然後 Throw 陳述式會擲回例外狀況表示是否驗證失敗。在這方面,它與不同需要,但它很類似需要 <T>。一般的 If 然後 Throw 陳述式也不是設定為可探索的合約工具 (產品和靜態檢查) 除非遵循藉由呼叫 EndContractBlock。當您使用 EndContractBlock 時,它必須是您叫用方法中的最後一個程式碼合約方法。沒有其他程式碼合約呼叫曾經可以依照它:

if (y == 0)
  throw new ArgumentException();
Contract.EndContractBlock();

此外,需要會自動繼承陳述式。 除非您也使用 EndContractBlock,不會繼承 If 然後 Throw 陳述式。 在傳統模式下,不被繼承 If 然後 Throw 合約。 而是您必須以手動方式執行合約繼承。 工具會試著警告如果它們不會偵測先決條件中覆寫重複和介面實作。

最後,請注意 ContractAbbreviators 不能包含任何 If 然後 Throw 陳述式,但您可以使用的合約驗證程式。 Abbreviators 只能包含一般的合約陳述式的引數驗證。

其他設定

在 [程式碼合約] 屬性頁中,如果您選取執行執行階段合約檢查] 選項中,您會啟用其他有用的選項。

如果您開啟合約失敗] 選項上判斷提示,合約的任何違規會造成判斷提示描述失敗的內容。 您會看到訊息方塊中顯示的內容與類似圖 2,會給予幾個選項可供選擇。 您可以,比方說,嘗試重新連接偵錯工具、 中止應用程式或只是略過失敗並繼續進行。

您可以使用此選項對偵錯組建只因為顯示的資訊是不可能是平均的一般使用者以有意義。 程式碼合約 API 有提供集中式的例外處理常式會擷取任何違規,留給您了解何者確實發生錯誤。 您收到的資訊來區別是否指定條件、 postcondition 或恆定性失敗,但是,只取用布林運算式、 可能的設定的錯誤訊息來描述錯誤。 換句話說,它是有點困難,您可以依正常程序復原從集中式的例外處理常式:

Contract.ContractFailed += CentralizedErrorHandler;

以下是一些程式碼,說明這個處理常式:

static void CentralizedErrorHandler(
  Object sender, ContractFailedEventArgs e) {
  Console.WriteLine("{0}: {1}; {2}", e.
FailureKind, e.Condition, e.Message);
  e.SetHandled();
}

如果您想要在執行階段擲回特定例外狀況,然後使用需要 <TException> 沒錯。您可能想要使用需要與集中式的處理常式如果您想要限制的使用縮小來偵錯組建或是如果您不需在乎例外狀況的實際型別為何。它通常足以只是為指示發生錯誤。比方說,在最高層級,許多應用程式有攔截的例外狀況的每個型別,找出如何重新啟動捕捉所有。

只有公用介面合約選項是指到您想要有強制執行的程式碼合約:每個方法或只有公用方法。如果您選取選項,然後產品會忽略私用和受保護成員上的程式碼合約陳述式,它只處理合約上的公用成員。

核取此選項意義如果在合併程式碼合約為您的整體設計的一部份,如此一來,到處都使用它們。不過,應用程式準備出貨時,作為一種最佳化您可以關閉檢查內部成員上的參數,因為沒有外部的程式碼將叫曾經用這些成員直接的額外負擔。

是否限制程式碼合約至組件的公用介面是一個好主意也取決於您如何撰寫了程式碼。如果您可以保證公用介面對較低層級的任何呼叫正確,它是一種最佳化。否則,請停用的合約以內部的方法可以轉換成出棘手的錯誤的來源。

呼叫站台需要檢查] 選項可做另一個最佳化案例。假設您正在編寫其他組件中的模組所使用的文件庫。基於效能考量,您停用執行階段檢查的合約。不過,只要您也建立合約參考組件,您會啟用呼叫端檢查要呼叫每個方法的合約。

合約參考組件可能包含類別,使用程式碼合約在每一個組件存在。它包含的合約陳述式的父組件,但沒有其他程式碼公開可見的介面。組件的建立可以排序和控制從程式碼合約屬性頁。

打算來叫用您的媒體櫃任何程式碼可能會參考您的合約參考組件,並無法自動匯入您的合約藉由只開啟的呼叫站台需要檢查的選項。在處理時的呼叫端程式碼,已經將匯入的每個方法呼叫外部組件所附的合約參考組件上的合約。合約在這種情況下,檢查在呼叫站台,呼叫端一邊 — 和維持一個選擇性的行為,可以開啟和關閉程式碼無法控制直接。

完成

程式碼合約是區域。NET Framework 值得一大堆更多的調查。我已經只是粗略介紹的設定選項這裡,甚至還沒有去到的靜態檢查程式使用。程式碼合約幫助您更清楚地設計應用程式和撰寫更簡潔的程式碼。

若要深入了解程式碼合約,請參閱 2009 年 6 月期 < 深究 CLR 逐 Melitta Andersen (msdn.microsoft.com/magazine/ee236408) 和 DevLabs 的程式碼合約站台 (msdn.microsoft.com/devlabs/dd491992)。您也可以找到有關的 Microsoft 研究的程式碼合約站台上的程式碼合約開發有趣的背景資訊 (research.microsoft.com/projects/contracts)。

Dino Esposito 是作者的 < 程式設計 Microsoft ASP。NET MVC"(在 [微軟出版品,2010年) 和 coauthor 的"Microsoft。NET:架構的企業應用程式 」 (在 [微軟出版品,2008年)。居住在義大利,Esposito 是在世界各地的產業活動演說。在 Twitter 上都追隨他twitter.com/despos

感謝至下列技術專家檢閱這份文件:Mike Barnett