.NET的Contract類庫是Declarative Programming實踐的一部分,可以對日常編程帶來很多好處:
Contract類本身已經在.NET 4.0之後集成進了System.Diagnostics.Contracts命名空間,但如果想使用Contract方法實現運行時的驗證,還需要單獨安裝一個VS插件。裝好之後,去項目屬性裡開啟運行時檢查:

這樣每次編譯項目的時候,插件裡的ccrewrite工具會將Contract方法編譯成有效的檢查代碼分別注入函數體的首尾。所以即使你把Contract.Ensures檢查放在函數開頭部分(這也是推薦做法),編譯之後這部分邏輯依然會出現在函數末尾,檢查函數結束條件是否滿足。
需要注意的是,如果想要在Debug和Release Build都使用運行時驗證功能,則需要在項目設置為Debug和Release編譯時,分別設置打開Runtime check。
Contract的基本使用包括Requires和Ensures,Requires在方法開始時檢查初始條件是否滿足,通常用來做參數驗證。Ensures方法用來在方法結束時檢查執行結果是否符合預期,比如可以放在Property set方法的末尾檢查Property是否被正確設置。
當檢查失敗時,默認會拋出ContractException,使用泛型的Requires和EnsuresOnThrow可以指定其他類型的異常。
public async void GetPage(string entryPageUrl)
{
Contract.Requires<ArgumentException>(Uri.IsWellFormedUriString(entryPageUrl, UriKind.Absolute));
...
}
Contract有一個很酷的feature,就是可以在接口裡定義一些檢查,要求所有的實現都滿足這些檢查條,這樣就不用在接口的每個實現裡分別定義相同的檢查邏輯了,非常的優雅,也符合Declaration Programming的初衷。
以下是示例代碼:
[ContractClass(typeof(IBookRepositoryContract))]
public interface IBookRepository
{
string BookTitle { get; set; }
void Create(string name, Stream blob);
}
[ContractClassFor(typeof(IBookRepository))]
sealed class IBookRepositoryContract : IBookRepository
{
public string BookTitle
{
get
{
return null;
}
set
{
Contract.Requires(!string.IsNullOrWhiteSpace(value), "Book title must not be empty.");
Contract.Requires(string.IsNullOrWhiteSpace(this.BookTitle), "Book title has already been set.");
}
}
public void Create(string name, Stream blob)
{
Contract.Requires<InvalidOperationException>(!string.IsNullOrWhiteSpace(this.BookTitle), "Book title hasn't been set");
}
}
這樣所有IBookRepository的實現類都無需再定義這些檢查了。
參考資料:
http://research.microsoft.com/en-us/projects/contracts/userdoc.pdf
http://blog.csdn.net/atfield/article/details/4465227
http://www.cnblogs.com/yangecnu/p/The-evolution-of-argument-validation-in-DotNet.html