首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

.NET 4.0 契約式程式設計完整指南:Precondition、Postcondition、Invariant 與靜態驗證

     編輯:關於.NET

什麼是契約式程式設計?

**「契約 = 方法/類別必須為真的條件集合」**。源自 Eiffel 語言 (Bertrand Meyer),核心三要素:

  • Precondition (前置條件):呼叫端必須滿足 (參數驗證)
  • Postcondition (後置條件):方法結束時保證成立 (回傳值、狀態)
  • Invariant (不變量):物件生命週期中永遠成立的條件

.NET 4.0 透過 System.Diagnostics.Contracts.Contract 靜態類別標準化此模型,並整合 **靜態驗證器 (Static Checker)** 與 **運行期檢查**。

為什麼 C++ 契約失敗而 .NET 成功?

核心 API 速查表

C++ 傳統做法.NET 4.0 Contract
巨量 #ifdef CONTRACT_FULL 混雜業務碼乾淨的 Contract.Requires(...) 呼叫
專案間巨集定義不一,無標準BCL 內建,跨專案一致
僅運行期斷言 (assert)**靜態驗證 + 運行期 + 文檔生成**
效能顧慮難以條件編譯移除Release 可自動移除非 Precondition 契約
契約類型方法用途運行期行為
斷言Contract.Assert(cond, msg?)程式中任意點必真Debug.Assert
假設Contract.Assume(cond, msg?)靜態驗證器假設為真同 Assert
前置條件Contract.Requires(cond, msg?)參數驗證、狀態檢查Debug.Assert + Environment.FastFail
前置條件(永不移除)Contract.RequiresAlways(...)公開 API 參數防禦所有建置皆檢查
後置條件Contract.Ensures(cond)正常返回保證同 Precondition
例外時後置條件Contract.EnsuresOnThrows<TEx>(cond)拋出特定例外時保證同 Precondition
不變量Contract.Invariant(cond)物件永恆條件每 public 方法結束檢查

實戰範例:有理數類別

using System.Diagnostics.Contracts;

public class RationalNumber
{
    private int _numerator;
    private int _denominator;

    public RationalNumber(int numerator, int denominator)
    {
        // Precondition: 分母不可為 0
        Contract.Requires(denominator != 0, "Denominator cannot be zero.");
        _numerator = numerator;
        _denominator = denominator;
    }

    public int Denominator
    {
        get
        {
            // Postcondition: 取得值時保證非 0
            Contract.Ensures(Contract.Result<int>() != 0);
            return _denominator;
        }
    }

    // Object Invariant: 物件內部永遠滿足分母非 0
    [ContractInvariantMethod]
    private void ObjectInvariant()
    {
        Contract.Invariant(_denominator != 0);
    }
}

後置條件進階特殊方法

方法用途範例
Contract.Result<T>()存取回傳值Contract.Ensures(Contract.Result<int>() > 0)
Contract.OldValue<T>(expr)存取方法入口時的值Contract.Ensures(Contract.OldValue(_count) < _count)
Contract.ValueAtReturn<T>(out param)存取 out 參數返回值Contract.Ensures(Contract.ValueAtReturn(out x) == 3)
Contract.ForAll(coll, pred)集合所有元素滿足謂詞Contract.Requires(Contract.ForAll(items, i => i != null))
Contract.Exists(coll, pred)集合存在元素滿足謂詞Contract.Ensures(Contract.Exists(list, x => x.IsValid))

OldValue 使用限制 (重要)

  • 僅能用於 Ensures / EnsuresOnThrows 內。
  • 不可巢狀:OldValue(OldValue(x)) ❌
  • 不可存取回傳值:OldValue(Contract.Result<int>()) ❌
  • 不可存取 out 參數:OldValue(outParam) ❌
  • ForAll/Exists 謂詞內不可存取謂詞參數:ForAll(0, n, i => OldValue(arr[i])) ✅ (索引器參數);ForAll(0, n, i => OldValue(i)) ❌

介面與抽象方法契約

介面/抽象方法無方法本體,需**契約類別**配合雙屬性:

[ContractClass(typeof(IAccountContract))]
public interface IAccount
{
    int Balance { get; }
    void Deposit(int amount);
}

[ContractClassFor(typeof(IAccount))]
sealed class IAccountContract : IAccount
{
    public int Balance
    {
        get
        {
            Contract.Ensures(Contract.Result<int>() >= 0);
            return default(int); // 必須提供 dummy return
        }
    }

    public void Deposit(int amount)
    {
        Contract.Requires(amount > 0);
        // 若需呼叫介面其他成員:
        IAccount self = this;
        Contract.Requires(self.Balance < 10000);
    }
}

契約屬性總覽

屬性目標用途
[ContractClass]介面/抽象類別指向契約類別
[ContractClassFor]契約類別指向被契約介面
[ContractInvariantMethod]方法標記不變量方法
[Pure]方法/委派無副作用,可用於契約中
[ContractVerification]組件/類別/成員(false) 停用靜態驗證
[ContractPublicPropertyName]私有欄位契約中以公開屬性名稱參考

靜態驗證器 (Static Checker) - 關鍵價值

  • **編譯期發現 Bug**:未執行程式即偵測 Precondition 違規、Invariant 破壞。
  • 整合 Visual Studio 建置流程 (需安裝 Code Contracts 擴充套件)。
  • 輸出警告/錯誤列表,可點擊跳轉原始碼。
  • 支援 [ContractVerification(false)] 逐步導入舊專案。

已知限制 (NET 4.0 版本)

效能與部署策略

  1. Debug 建置:啟用所有契約檢查 (Pre/Post/Invariant/Assert)。
  2. Release 建置:預設僅保留 Requires (前置條件);可組態保留更多。
  3. 公開 API:使用 RequiresAlways 確保參數驗證永不移除。
  4. 內部邏輯:用 Requires,Release 可移除提升效能。

與 D 語言契約比較

D 語言原生 in/out/invariant 區塊,語法更優雅、編譯器深度整合。.NET 契約雖為庫級實現,但配合**靜態驗證器**已達實用水準,且優勢在於**生態整合** (VS、CI/CD、文檔生成)。

結語

契約式程式設計將「文檔中的規範」搬進「可驗證的程式碼」。.NET 4.0 提供成熟工具鏈:Contract.Requires/Ensures/Invariant + 靜態驗證器。建議:**公開 API 必加 Precondition、核心領域模型加 Invariant、關鍵演算法加 Postcondition**,逐步建立「有契約的代碼庫」,大幅降低防禦性程式碼、提早攔截邏輯錯誤、改善團隊溝通精度。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
限制說明
值類型 (struct) 的 Invariant 被忽略CLR 不支援 struct 完成項/攔截
靜態驗證不支援 ForAll/Exists運行期仍檢查
C# Iterator (yield return) 契約問題編譯器產生狀態機,Precondition 位置錯位
非公用方法不檢查 Invariant設計決策:僅驗證公開介面