小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

C# 中參數(shù)驗(yàn)證方式的演變

 ThinkTank_引擎 2018-08-14

一般在寫方法的時(shí)候,第一步就是進(jìn)行參數(shù)驗(yàn)證,這也體現(xiàn)了編碼者的細(xì)心和縝密,但是在很多時(shí)候這個(gè)過(guò)程很枯燥和乏味,比如在拿到一個(gè)API設(shè)計(jì)文檔的時(shí)候,通常會(huì)規(guī)定類型參數(shù)是否允許為空,如果是字符可能有長(zhǎng)度限制,如果是整數(shù)可能需要判斷范圍,如果是一些特殊的類型比如電話號(hào)碼,郵件地址等,可能需要使用正則表達(dá)式進(jìn)行判斷。

通常,我們一般都是在方法開(kāi)始的地方進(jìn)行條件判斷,然后拋出合適的異常,這是最普通和通用的做法,但是在.NET中,利用一些語(yǔ)言特性和類庫(kù),可以使用一些其他的方式將我們從復(fù)雜繁瑣的工作中解放出來(lái)。本文逐一介紹能夠用來(lái)進(jìn)行參數(shù)驗(yàn)證的方式,他們包括直接判斷語(yǔ)句,幫助類,擴(kuò)展方法,Customer Attribute,Enterprise Liberary,Debug.Assert,Code Contract等??梢钥吹皆?NET中隨著版本的演化,逐步添加了很多聲明式編程(Declarative programming)的風(fēng)格,這樣的代碼會(huì)直接表明what而不是how,從而使得代碼更加清晰和易維護(hù)。

現(xiàn)在來(lái)看下這些參數(shù)驗(yàn)證的方法。

一 一般的方法

假設(shè)我們有一個(gè)方法如下,用來(lái)進(jìn)行登記注冊(cè),需要傳入姓名和年齡。

public bool Register(string name, int age)
{
    //insert into db
}

當(dāng)然,不是傳進(jìn)什么東西都能調(diào)用我們的方法。一般地,我們首先要做的是,對(duì)參數(shù)進(jìn)行合法性驗(yàn)證,比如如果要參加上海國(guó)際馬拉松比賽,需要年齡大于10歲小于70歲。一般的驗(yàn)證方法如下:

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
}

我們會(huì)發(fā)現(xiàn),如果每一個(gè)方法都這樣判斷的話,非常麻煩和繁瑣。于是就想到把他提取到一個(gè)幫助方法中。

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);
        }
    }
}

這樣,在所有需要進(jìn)行區(qū)間驗(yàn)證和非空驗(yàn)證的地方,調(diào)用這個(gè)幫助類中的方法即可。

public bool Register(string name, int age)
{
    ArgumentHelper.RequireNotNullOrEmpty(name,"name");
    ArgumentHelper.RequireRange(age,10,70,"age");   
    //insert into db
}

在C#3.0 中,引入了擴(kuò)展方法,因此可以以一種更優(yōu)雅的方式來(lái)進(jìn)行參數(shù)驗(yàn)證,我們將前面的幫助方法改寫如下:

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);
        }
    }
}

這樣,我們的驗(yàn)證變成了如下:

public bool Register(string name, int age)
{
   name.RequireNotNullOrEmpty("name");
   age.RequireRange(10,70,"age");
    //insert into db
}

有了擴(kuò)展方法,就可以寫出很多類似LINQ的比較流暢的驗(yàn)證語(yǔ)句來(lái)。一些類似的驗(yàn)證類庫(kù)也提供了類似功能。如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;
}

利用擴(kuò)展方法也可以寫出如下圖中這種比較搞笑的語(yǔ)句。

Who write like this

二 使用類庫(kù)或者框架

除了自己寫方法之外,一些類庫(kù)和框架也提供了參數(shù)驗(yàn)證的模塊。

Enterprise Liberary

微軟企業(yè)庫(kù)(Enterprise Liberary)中提供了一個(gè)名為Validation Application Block的組件,專門用來(lái)驗(yàn)證。安裝之后,運(yùn)行EntLibConfig.exe 就可以使用界面的方式來(lái)添加驗(yàn)證

還是以前面的代碼為例子。我們將name和age封裝為一個(gè)名為Person的類的字段,然后使用企業(yè)庫(kù)來(lái)進(jìn)行驗(yàn)證。允許EntLibConfig.exe,加載我們編譯好的dll或者exe,然后選擇需要驗(yàn)證的字段或者方法,然后添加合適的驗(yàn)證規(guī)則,如下圖:

Validation with Enterprise Liberary

完成之后,保存為app.config文件,該文件內(nèi)容如下:

<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>

可以看到企業(yè)庫(kù)實(shí)際上是生成一個(gè)app.config文件,然后在文件中寫入了參數(shù)的驗(yàn)證條件,然后在運(yùn)行的時(shí)候引用企業(yè)庫(kù)的相關(guān)dll進(jìn)行驗(yàn)證。參數(shù)驗(yàn)證是可以配置的,具體的使用方法如下:

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("驗(yàn)證出錯(cuò) {0}:" + validateResult.ElementAt(i).Message, i + 1);
        }
    }
    else
    {
        Console.WriteLine("正確");
    }
    Console.ReadKey();
}

運(yùn)行結(jié)果如下:

Validate using enterprise liberary

ASP.NET MVC

還可以利用自定義屬性(Customer Attribute)來(lái)進(jìn)行參數(shù)驗(yàn)證,ASP.NET MVC 的Model中就是使用數(shù)據(jù)標(biāo)記(Data Annotations)這種屬性來(lái)進(jìn)行驗(yàn)證。Data Annotations其實(shí)是一系列繼承自Attribute的可以用在類或者類的屬性上的自定義屬性類。

System.ComponentModel.DataAnnotations 程序集中內(nèi)建有一些諸如 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; }
}

很簡(jiǎn)潔,在編寫實(shí)體類的時(shí)候,就可以順便把驗(yàn)證規(guī)則給定了。這樣,我們?cè)趯?shí)例化Person類,然后調(diào)用框架中相應(yīng)的方法,如果不滿足條件,就會(huì)拋出相應(yīng)的異常。

PostSharp

一些商業(yè)軟件,更是將利用屬性進(jìn)行驗(yàn)證做到了極致,比如PostSharp這款商業(yè)軟件。下面是該網(wǎng)站的宣傳頁(yè):

 postsharp

可以看到,在方法的參數(shù)中,可以在前面使用自定義屬性來(lái)標(biāo)記,然后在系統(tǒng)運(yùn)行的時(shí)候進(jìn)行動(dòng)態(tài)的驗(yàn)證。

PostSharp使用的是一種所謂靜態(tài)注入的方式,也就是在編譯好的程序集中的類型或者某個(gè)方法里注入IL代碼,是在代碼編譯的時(shí)候,而不是在運(yùn)行時(shí)注入的。Visual Studio通過(guò)MSBuild來(lái)執(zhí)行生成過(guò)程,PostSharp是把自己作為一系列的Build Task來(lái)插入到代碼生成過(guò)程中來(lái)的。其原理可以參看 .NET下的AOP: PostSharp 原理分析 這篇文章。這里引用了文中的一幅圖,很形象的說(shuō)明了PostSharp的原理:

Post

自己動(dòng)手

其實(shí)使用屬性來(lái)進(jìn)行驗(yàn)證很簡(jiǎn)單,我們也可以自己動(dòng)手來(lái)實(shí)現(xiàn)類似PostSharp的功能,當(dāng)然,在使用Customer Attribute之前,首先您需要了解Attribute這個(gè)類, 中文的話,您可以參考CSDN上的Attribute在.net編程中的應(yīng)用一些列6篇文章。下面就介紹如何實(shí)現(xiàn)PostSharp中的使用自定義屬性對(duì)參數(shù)進(jìn)行標(biāo)記驗(yàn)證。其實(shí)您看過(guò)ASP.NET MVC 中的System.ComponentModel.DataAnnotations應(yīng)該就可以知道該怎么實(shí)現(xiàn)了。

首先,新建一個(gè)ArgumentValidationAttribute抽象類。因?yàn)榘凑占s定,所有繼承自Attribute的類名稱后面必須帶有Attribute。這個(gè)類中只有一個(gè)抽象方法Validate,用來(lái)驗(yàn)證。

public abstract  class ArgumentValidationAttribute:Attribute
{
    public abstract void Validate(object value, string argumentName);
}

然后,我們定義一個(gè)用來(lái)驗(yàn)證非空的自定義屬性NotNullAttribute,注意到在該類上,我們使用了AttributeUsage屬性,在其構(gòu)造函數(shù)參數(shù)中,我們傳入了 AttributeTargets.Parameter 這個(gè)枚舉,表明該標(biāo)記只能用在方法的參數(shù)上。

[AttributeUsage(AttributeTargets.Parameter)]
public class NotNullAttribute : ArgumentValidationAttribute
{  
    public override void Validate(object value, string argumentName)
    {
        if (value == null)
            throw  new ArgumentNullException(argumentName);
    }
}

然后定義了一個(gè)用來(lái)驗(yàn)證范圍的InRangeAttribute,這里我們定義了一個(gè)構(gòu)造函數(shù),使得可以傳入一個(gè)區(qū)間范圍。

[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));
        }
    }
}

有了上面兩個(gè)類,我們還需要在一個(gè)大的框架類驗(yàn)證調(diào)用這些驗(yàn)證方法,通常,我們會(huì)使用諸如接口注入的方式來(lái)實(shí)現(xiàn)。這里僅列出關(guān)鍵部分。

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();
    }
}

然后,再回頭看我們的代碼,我們首先抽象一個(gè)IRegister接口:

public interface IRegister
{
    void Register([NotNull] string name, [InRange(10, 70)] int age);
}

可以看到,現(xiàn)在接口中的方法,參數(shù)前面已經(jīng)可以寫我們之前定義的用于驗(yàn)證功能的屬性了,接口方法中定義了參數(shù)的驗(yàn)證規(guī)則之后,所有實(shí)現(xiàn)該接口的方法中就不需要再次定義了。我們的注冊(cè)類只需要實(shí)現(xiàn)該接口,然后再執(zhí)行Register之前,統(tǒng)一驗(yàn)證即可。

public class ShMarathon : IRegister
{

    public void Register(string name, int age)
    {
        //doesn't need to validate
        //insert into db
    }
}

這種方式其實(shí)是AOP(面向方面編程)的一種思想,更多的資料您可以參考AOP in .NET: Practical Aspect-Oriented ProgrammingDependency Injection in .NET

三 Code Contract

Code Contracts 是微軟研究院開(kāi)發(fā)的一個(gè)編程類庫(kù),我最早看到是在C# In Depth 的第二版中,當(dāng)時(shí).NET 4.0還沒(méi)有出來(lái),當(dāng)時(shí)是作為一個(gè)第三方類庫(kù)存在的,到了.NET 4.0之后,已經(jīng)加入到了.NET BCL中,該類存在于System.Diagnostics.Contracts 這個(gè)命名空間中。

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 中契約式設(shè)計(jì)和編程變得更加容易,Contract中的這些靜態(tài)方法方法包括

  1. Requires:函數(shù)入口處必須滿足的條件
  2. Ensures:函數(shù)出口處必須滿足的條件
  3. Invariants:所有成員函數(shù)出口處都必須滿足的條件
  4. Assertions:在某一點(diǎn)必須滿足的條件
  5. Assumptions:在某一點(diǎn)必然滿足的條件,用來(lái)減少不必要的警告信息

Code Contract 的使用文檔您可以從官網(wǎng)下載到。為了方便使用Visual Studio開(kāi)發(fā)。我們可以安裝一個(gè)Code Contracts Editor Extensions插件。安裝完了之后,點(diǎn)擊Visual Studio中的項(xiàng)目屬性,可以看到如下豐富的選擇項(xiàng):

code contract for visual studio

現(xiàn)在,在我們的Register方法中,可以使用Contract來(lái)進(jìn)行判斷,使用方式和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;
}

當(dāng)我們傳入錯(cuò)誤的age值的時(shí)候,就會(huì)報(bào)錯(cuò):

PreconditonFailed

Contract和Debug.Assert有些地方相似:

  1. 都提供了運(yùn)行時(shí)支持:這些Contracts都是可以被運(yùn)行的,并且一旦條件不被滿足,會(huì)彈出類似Assert的一樣的對(duì)話框報(bào)錯(cuò),如下:
  2. 都可以在隨意的在代碼中關(guān)閉打開(kāi)。

但是Contract有更多和更強(qiáng)大的功能:

  1. Contracts的意圖更加清晰,通過(guò)不同的Requires/Ensures等等調(diào)用,代表不同類型的條件,比單純的Assert更容易理解和進(jìn)行自動(dòng)分析
  2. Contracts的位置更加統(tǒng)一,將3種不同條件都放在代碼的開(kāi)始處,而非散見(jiàn)在函數(shù)的開(kāi)頭和結(jié)尾,便于查找和分析。
  3. 不同的開(kāi)發(fā)人員、不同的小組、不同的公司、不同的庫(kù)可能都會(huì)有自己的Assert,這就大大增加了自動(dòng)分析的難度,也不利于開(kāi)發(fā)人員編寫代碼。而Contracts直接被.NET 4.0支持,是統(tǒng)一的。
  4. 它提供了靜態(tài)分析支持,這個(gè)我們可以通過(guò)配置面板看到,通過(guò)靜態(tài)分析Contracts,靜態(tài)分析工具可以比較容易掌握函數(shù)的各種有關(guān)信息,甚至可以作為Intellisense

Contract中包含了三個(gè)工具:

  • ccrewrite, 通過(guò)向程序集中些如二進(jìn)制數(shù)據(jù),來(lái)支持運(yùn)行時(shí)檢測(cè)
  • cccheck, 運(yùn)行時(shí)檢測(cè)
  • ccdoc, 將Contract自動(dòng)生成XML文檔

下圖是Contract的原理,圖片來(lái)自 .NET 4.0中的新功能介紹:契約式設(shè)計(jì) (Design By Contracts) 這篇文章, 這也是為什么要比Debug.Assert強(qiáng)大的原因,我們只需要將所有的執(zhí)行前判斷和執(zhí)行后判斷的條件,寫到一個(gè)地方,然后再編譯代碼的時(shí)候,ccrewrite 會(huì)幫我們生成相應(yīng)的代碼,保存起始值,并將相應(yīng)的代碼插入到方法的合適位置。使得我們的代碼更加整潔和容易維護(hù)。

Code Contract

四 總結(jié)

本文簡(jiǎn)單介紹了在.NET 中用來(lái)進(jìn)行方法參數(shù)驗(yàn)證的各種方式,包括傳統(tǒng)的在方法執(zhí)行前編寫判斷語(yǔ)句,提取到公共幫助類中,使用擴(kuò)展方法,以及一些類庫(kù)如Enterprise Liberary,PostSharp,ASP.NET MVC然后實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的利用自定義屬性來(lái)進(jìn)行方法參數(shù)驗(yàn)證的例子,最后介紹了一下.NET 4.0種的Code Contract,在開(kāi)發(fā)中這些驗(yàn)證方式能夠統(tǒng)一我們的方法參數(shù)驗(yàn)證,在一定的程序上可以減少工作量,希望本文對(duì)您有所幫助。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多