程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C# 中參數驗證方式的演變

C# 中參數驗證方式的演變

編輯:C#入門知識

一般在寫方法的時候,第一步就是進行參數驗證,這也體現了編碼者的細心和缜密,但是在很多時候這個過程很枯燥和乏味,比如在拿到一個API設計文檔的時候,通常會規定類型參數是否允許為空,如果是字符可能有長度限制,如果是整數可能需要判斷范圍,如果是一些特殊的類型比如電話號碼,郵件地址等,可能需要使用正則表達式進行判斷。   通常,我們一般都是在方法開始的地方進行條件判斷,然後拋出合適的異常,這是最普通和通用的做法,但是在.NET中,利用一些語言特性和類庫,可以使用一些其他的方式將我們從復雜繁瑣的工作中解放出來。本文逐一介紹能夠用來進行參數驗證的方式,他們包括直接判斷語句,幫助類,擴展方法,Customer Attribute,Enterprise Liberary,Debug.Assert,Code Contract等。可以看到在.NET中隨著版本的演化,逐步添加了很多聲明式編程(Declarative programming)的風格,這樣的代碼會直接表明what而不是how,從而使得代碼更加清晰和易維護。   現在來看下這些參數驗證的方法。   一 一般的方法 假設我們有一個方法如下,用來進行登記注冊,需要傳入姓名和年齡。   public bool Register(string name, int age) {     //insert into db } 當然,不是傳進什麼東西都能調用我們的方法。一般地,我們首先要做的是,對參數進行合法性驗證,比如如果要參加上海國際馬拉松比賽,需要年齡大於10歲小於70歲。一般的驗證方法如下:   public bool Register(string name, int age) {     if (string.IsNullOrEmpty(name))     {         throw new ArgumentException("name should not be empty", "name");     }     if (age < 10 || age > 70)     {         throw new ArgumentException("the age must between 10 and 70","age");     }     //insert into db } 我們會發現,如果每一個方法都這樣判斷的話,非常麻煩和繁瑣。於是就想到把他提取到一個幫助方法中。   public static class ArgumentHelper {     public static void RequireRange(int value, int minValue, int maxValue, string argumentName)     {         if (value > maxValue || value < minValue)         {             throw new ArgumentException(string.Format("The value must be between {0} and {1}", minValue, maxValue),                 argumentName);         }     }       public static void RequireNotNullOrEmpty(string value, string argumentName)     {         if (string.IsNullOrEmpty(value))         {             throw new ArgumentException("The value can't be null or empty", argumentName);         }     } } 這樣,在所有需要進行區間驗證和非空驗證的地方,調用這個幫助類中的方法即可。   public bool Register(string name, int age) {     ArgumentHelper.RequireNotNullOrEmpty(name,"name");     ArgumentHelper.RequireRange(age,10,70,"age");        //insert into db } 在C#3.0 中,引入了擴展方法,因此可以以一種更優雅的方式來進行參數驗證,我們將前面的幫助方法改寫如下:   public static class ArgumentHelper {     public static void RequireRange(this int value, int minValue, int maxValue, string argumentName)     {         if (value > maxValue || value < minValue)         {             throw new ArgumentException(string.Format("The value must be between {0} and {1}", minValue, maxValue),                 argumentName);         }     }       public static void RequireNotNullOrEmpty(this string value, string argumentName)     {         if (string.IsNullOrEmpty(value))         {             throw new ArgumentException("The value can't be null or empty", argumentName);         }     } } 這樣,我們的驗證變成了如下:   public bool Register(string name, int age) {    name.RequireNotNullOrEmpty("name");    age.RequireRange(10,70,"age");     //insert into db } 有了擴展方法,就可以寫出很多類似LINQ的比較流暢的驗證語句來。一些類似的驗證類庫也提供了類似功能。如FluentValidation,CuttingEdge.Conditions等。比如這裡取自CuttingEdge.Condition 裡面的例子。   public ICollection GetData(Nullable<int> id, string xml, IEnumerable<int> col) {     // Check all preconditions:     Condition.Requires(id, "id")         .IsNotNull()          // throws ArgumentNullException on failure         .IsInRange(1, 999)    // ArgumentOutOfRangeException on failure         .IsNotEqualTo(128);   // throws ArgumentException on failure       Condition.Requires(xml, "xml")         .StartsWith("<data>") // throws ArgumentException on failure         .EndsWith("</data>") // throws ArgumentException on failure         .Evaluate(xml.Contains("abc") || xml.Contains("cba")); // arg ex       Condition.Requires(col, "col")         .IsNotNull()          // throws ArgumentNullException on failure         .IsEmpty()            // throws ArgumentException on failure         .Evaluate(c => c.Contains(id.Value) || c.Contains(0)); // arg ex       // Do some work       // Example: Call a method that should not return null     object result = BuildResults(xml, col);       // Check all postconditions:     Condition.Ensures(result, "result")         .IsOfType(typeof(ICollection)); // throws PostconditionException on failure       return (ICollection)result; } 利用擴展方法也可以寫出如下圖中這種比較搞笑的語句。   Who write like this   二 使用類庫或者框架 除了自己寫方法之外,一些類庫和框架也提供了參數驗證的模塊。   Enterprise Liberary 微軟企業庫(Enterprise Liberary)中提供了一個名為Validation Application Block的組件,專門用來驗證。安裝之後,運行EntLibConfig.exe 就可以使用界面的方式來添加驗證   還是以前面的代碼為例子。我們將name和age封裝為一個名為Person的類的字段,然後使用企業庫來進行驗證。允許EntLibConfig.exe,加載我們編譯好的dll或者exe,然後選擇需要驗證的字段或者方法,然後添加合適的驗證規則,如下圖:   Validation with Enterprise Liberary   完成之後,保存為app.config文件,該文件內容如下:   <configuration>     <configSections>         <section name="validation" type="Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.ValidationSettings, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" />     </configSections>     <validation>         <type name="ValidationConsole.Program+Person" defaultRuleset="Validation Ruleset"             assemblyName="ValidationConsole, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">             <ruleset name="Validation Ruleset">                 <properties>                     <property name="Name">                         <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.NotNullValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"                             messageTemplate="姓名不能為空" name="Not Null Validator" />                     </property>                     <property name="Age">                         <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.RangeValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"                             culture="zh-CN" lowerBound="10" lowerBoundType="Exclusive"                             upperBound="70" messageTemplate="Age should between&#xD;&#xA;10 and 70&#xD;&#xA;"                             name="Range Validator" />                     </property>                 </properties>             </ruleset>         </type>     </validation> </configuration> 可以看到企業庫實際上是生成一個app.config文件,然後在文件中寫入了參數的驗證條件,然後在運行的時候引用企業庫的相關dll進行驗證。參數驗證是可以配置的,具體的使用方法如下:   public class Person {     public string Name { get; set; }     public int Age { get; set; }       public Person(string name, int age)     {         this.Name = name;         this.Age = age;     } } static void Main(string[] args) {     Validator<Person> customerValidator = ValidationFactory.CreateValidator<Person>("Validation Ruleset");     Person myCustomer = new Person("admin",9);       ValidationResults validateResult = customerValidator.Validate(myCustomer);       if (!validateResult.IsValid)     {         for (int i = 0; i < validateResult.Count; i++)         {             Console.WriteLine("驗證出錯 {0}:" + validateResult.ElementAt(i).Message, i + 1);         }     }     else     {         Console.WriteLine("正確");     }     Console.ReadKey(); } 運行結果如下:   Validate using enterprise liberary   ASP.NET MVC 還可以利用自定義屬性(Customer Attribute)來進行參數驗證,ASP.NET MVC 的Model中就是使用數據標記(Data Annotations)這種屬性來進行驗證。Data Annotations其實是一系列繼承自Attribute的可以用在類或者類的屬性上的自定義屬性類。   System.ComponentModel.DataAnnotations 程序集中內建有一些諸如 Required, Range, RegularExpression and StringLength的類, 這些自定義屬性類都繼承自ValidationAttribute抽象類:   public abstract class ValidationAttribute : Attribute {     public string ErrorMessage { get; set; }       public virtual bool IsValid(object value);       protected virtual ValidationResult IsValid(object value, ValidationContext     validationContext);       // other members } 如果在ASP.NET MVC 中,我們的Person Model就可以寫成如下:   public class Person {     [Required]     public string Name { get; set; }     [Range(10, 70)]     public int Age { get; set; } } 很簡潔,在編寫實體類的時候,就可以順便把驗證規則給定了。這樣,我們在實例化Person類,如果不滿足條件,就會拋出相應的異常。   PostSharp 一些商業軟件,更是將利用屬性進行驗證做到了極致,比如PostSharp這款商業軟件。下面是該網站的宣傳頁:    postsharp   可以看到,在方法的參數中,可以在前面使用自定義屬性來標記,然後在系統運行的時候進行動態的驗證。   PostSharp使用的是一種所謂靜態注入的方式,也就是在編譯好的程序集中的類型或者某個方法裡注入IL代碼,是在代碼編譯的時候,而不是在運行時注入的。Visual Studio通過MSBuild來執行生成過程,PostSharp是把自己作為一系列的Build Task來插入到代碼生成過程中來的。其原理可以參看 .NET下的AOP: PostSharp 原理分析 這篇文章。這裡引用了文中的一幅圖,很形象的說明了PostSharp的原理:   Post   自己動手 其實使用屬性來進行驗證很簡單,我們也可以自己動手來實現類似PostSharp的功能,當然,在使用Customer Attribute之前,首先您需要了解Attribute這個類, 中文的話,您可以參考CSDN上的Attribute在.net編程中的應用這一些列6篇文章。下面就介紹如何實現PostSharp中的使用自定義屬性對參數進行標記驗證。其實您看過ASP.NET MVC 中的System.ComponentModel.DataAnnotations應該就可以知道該怎麼實現了。   首先,新建一個ArgumentValidationAttribute抽象類。因為按照約定,所有繼承自Attribute的類名稱後面必須帶有Attribute。這個類中只有一個抽象方法Validate,用來驗證。   public abstract  class ArgumentValidationAttribute:Attribute {     public abstract void Validate(object value, string argumentName); } 然後,我們定義一個用來驗證非空的自定義屬性NotNullAttribute,注意到在該類上,我們使用了AttributeUsage屬性,在其構造函數參數中,我們傳入了 AttributeTargets.Parameter 這個枚舉,表明該標記只能用在方法的參數上。   [AttributeUsage(AttributeTargets.Parameter)] public class NotNullAttribute : ArgumentValidationAttribute {       public override void Validate(object value, string argumentName)     {         if (value == null)             throw  new ArgumentNullException(argumentName);     } } 然後定義了一個用來驗證范圍的InRangeAttribute,這裡我們定義了一個構造函數,使得可以傳入一個區間范圍。   [AttributeUsage(AttributeTargets.Parameter)] public class InRangeAttribute : ArgumentValidationAttribute {     private int min;     private int max;       public InRangeAttribute(int min, int max)     {         this.min = min;         this.max = max;     }       public override void Validate(object value, string argumentName)     {         int intValue = (int)value;         if (intValue < min || intValue > max)         {             throw new ArgumentOutOfRangeException(argumentName,string.Format("min={0},max={1}",min,max));         }     } } 有了上面兩個類,我們還需要在一個大的框架類驗證調用這些驗證方法,通常,我們會使用諸如接口注入的方式來實現。這裡僅列出關鍵部分。   public class ValidationInterceptor : IInterceptor {     public void Intercept(IInvocation invocation)     {         ParameterInfo[] parameters = invocation.Method.GetParameters();         for (int index = 0; index < parameters.Length; index++)         {             var paramInfo = parameters[index];             var attributes = paramInfo.GetCustomAttributes(typeof(ArgumentValidationAttribute), false);               if (attributes.Length == 0)                 continue;               foreach (ArgumentValidationAttribute attr in attributes)             {                 attr.Validate(invocation.Arguments[index], paramInfo.Name);             }         }           invocation.Proceed();     } } 然後,再回頭看我們的代碼,我們首先抽象一個IRegister接口:   public interface IRegister {     void Register([NotNull] string name, [InRange(10, 70)] int age); } 可以看到,現在接口中的方法,參數前面已經可以寫我們之前定義的用於驗證功能的屬性了,接口方法中定義了參數的驗證規則之後,所有實現該接口的方法中就不需要再次定義了。我們的注冊類只需要實現該接口,然後再執行Register之前,統一驗證即可。   public class ShMarathon : IRegister {       public void Register(string name, int age)     {         //doesn't need to validate         //insert into db     } } 這種方式其實是AOP(面向方面編程)的一種思想,更多的資料您可以參考AOP in .NET: Practical Aspect-Oriented Programming和Dependency Injection in .NET   三 Code Contract Code Contracts 是微軟研究院開發的一個編程類庫,我最早看到是在C# In Depth 的第二版中,當時.NET 4.0還沒有出來,當時是作為一個第三方類庫存在的,到了.NET 4.0之後,已經加入到了.NET BCL中,該類存在於System.Diagnostics.Contracts 這個命名空間中。   namespace System.Diagnostics.Contracts {     // Summary:     //     Contains static methods for representing program contracts such as preconditions,     //     postconditions, and object invariants.     public static class Contract     {         public static event EventHandler<ContractFailedEventArgs> ContractFailed;         public static void Assert(bool condition);         public static void Assert(bool condition, string userMessage);         public static void Assume(bool condition);         public static void Assume(bool condition, string userMessage);         public static void EndContractBlock();         public static void Ensures(bool condition);         public static void Ensures(bool condition, string userMessage);         public static void EnsuresOnThrow<TException>(bool condition) where TException : Exception;         public static void EnsuresOnThrow<TException>(bool condition, string userMessage) where TException : Exception;         public static bool Exists<T>(IEnumerable<T> collection, Predicate<T> predicate);         public static bool Exists(int fromInclusive, int toExclusive, Predicate<int> predicate);         public static bool ForAll<T>(IEnumerable<T> collection, Predicate<T> predicate);         public static bool ForAll(int fromInclusive, int toExclusive, Predicate<int> predicate);         public static void Invariant(bool condition);         public static void Invariant(bool condition, string userMessage);         public static T OldValue<T>(T value);         public static void Requires<TException>(bool condition) where TException : Exception;         public static void Requires(bool condition);         public static void Requires(bool condition, string userMessage);         public static void Requires<TException>(bool condition, string userMessage) where TException : Exception;         public static T Result<T>();         public static T ValueAtReturn<T>(out T value);     } } Code Contract 使得.NET 中契約式設計和編程變得更加容易,Contract中的這些靜態方法方法包括   Requires:函數入口處必須滿足的條件 Ensures:函數出口處必須滿足的條件 Invariants:所有成員函數出口處都必須滿足的條件 Assertions:在某一點必須滿足的條件 Assumptions:在某一點必然滿足的條件,用來減少不必要的警告信息 Code Contract 的使用文檔您可以從官網下載到。為了方便使用Visual Studio開發。我們可以安裝一個Code Contracts Editor Extensions插件。安裝完了之後,點擊Visual Studio中的項目屬性,可以看到如下豐富的選擇項:   code contract for visual studio   現在,在我們的Register方法中,可以使用Contract來進行判斷,使用方式和Debug.Assert類似:   public static bool Register(string name, int age) {     Contract.Requires(!String.IsNullOrEmpty(name));     Contract.Requires(age > 10 && age < 70);     //insert into db     return false; } 當我們傳入錯誤的age值的時候,就會報錯:   PreconditonFailed   Contract和Debug.Assert有些地方相似:   都提供了運行時支持:這些Contracts都是可以被運行的,並且一旦條件不被滿足,會彈出類似Assert的一樣的對話框報錯,如下: 都可以在隨意的在代碼中關閉打開。 但是Contract有更多和更強大的功能:   Contracts的意圖更加清晰,通過不同的Requires/Ensures等等調用,代表不同類型的條件,比單純的Assert更容易理解和進行自動分析 Contracts的位置更加統一,將3種不同條件都放在代碼的開始處,而非散見在函數的開頭和結尾,便於查找和分析。 不同的開發人員、不同的小組、不同的公司、不同的庫可能都會有自己的Assert,這就大大增加了自動分析的難度,也不利於開發人員編寫代碼。而Contracts直接被.NET 4.0支持,是統一的。 它提供了靜態分析支持,這個我們可以通過配置面板看到,通過靜態分析Contracts,靜態分析工具可以比較容易掌握函數的各種有關信息,甚至可以作為Intellisense Contract中包含了三個工具:   ccrewrite, 通過向程序集中些如二進制數據,來支持運行時檢測 cccheck, 運行時檢測 ccdoc, 將Contract自動生成XML文檔 下圖是Contract的原理,圖片來自 .NET 4.0中的新功能介紹:契約式設計 (Design By Contracts) 這篇文章, 這也是為什麼要比Debug.Assert強大的原因,我們只需要將所有的執行前判斷和執行後判斷的條件,寫到一個地方,然後再編譯代碼的時候,ccrewrite 會幫我們生成相應的代碼,保存起始值,並將相應的代碼插入到方法的合適位置。使得我們的代碼更加整潔和容易維護。   Code Contract   四 總結 本文簡單介紹了在.NET 中用來進行方法參數驗證的各種方式,包括傳統的在方法執行前編寫判斷語句,提取到公共幫助類中,使用擴展方法,以及一些類庫如Enterprise Liberary,PostSharp,ASP.NET MVC然後實現了一個簡單的利用自定義屬性來進行方法參數驗證的例子,最後介紹了一下.NET 4.0種的Code Contract,在開發中這些驗證方式能夠統一我們的方法參數驗證,在一定的程序上可以減少工作量,希望本文對您有所幫助。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved