.NET的Contract類庫是Declarative Programming實踐的一部分,可以對日常編程帶來很多好處:
- 提高代碼可讀性,使用者一看Require, Ensure就知道這方法接受什么輸入,產生什么輸出。
- 減少重復的驗證代碼
- 配合第三方工具,可以方便靜態代碼分析和單元測試,方便產生API文檔,這些功能可以參見Code Contract主頁
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