.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