使用优雅方式对参数验证进行处理

作者: 365bet体育官网  发布:2019-11-04

我们在常常的接口函数开荒中,为了安全性,大家都亟待对传播的参数举行验证,确认保证参数依据大家所企盼的范围输入,固然在约束之外,如空值,不适合的项目等等,都应该提交至极或不当提示音讯。这一个参数的求证管理有多种方式,最为轻松的法子正是行使准绳语句对参数实行判断,那样的论断代码固然轻松领会,但相比较肥胖,假如对多个参数、八个标准实行拍卖,那么代码就不行肥胖难以维护了,本篇小说通过解析两种不一样的参数验证办法,最后使用较为高雅的艺术开展管理。

平时性会分明类型参数是或不是允许为空,假使是字符大概有长度约束,假诺是整数也许需求看清范围,倘诺是部分出奇的类别例如电话号码,邮件地址等,大概需求利用正则表明式举行判别。参谋小说《C# 中参数验证办法的演化》中文章的牵线,我们对参数的表达措施有两种。

1、常规方法的参数验证

貌似大家正是对章程的参数使用原则语句的办法张开判断,如下函数所示。

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 void Initialize(string name, int id)
{
    if (string.IsNullOrEmpty(value))
        throw new ArgumentException("name");
    if (id < 0) 
        throw new ArgumentOutOfRangeException("id");
    // Do some work here.
}

借使复杂的参数校验,那么代码就相比肥胖

void TheOldFashionWay(int id, IEnumerable<int> col, 
    DayOfWeek day)
{
    if (id < 1)
    {
        throw new ArgumentOutOfRangeException("id", 
            String.Format("id should be greater " +
            "than 0. The actual value is {0}.", id));
    }

    if (col == null)
    {
        throw new ArgumentNullException("col",
            "collection should not be empty");
    }

    if (col.Count() == 0)
    {
        throw new ArgumentException(
            "collection should not be empty", "col");
    }

    if (day >= DayOfWeek.Monday &&
        day <= DayOfWeek.Friday)
    {
        throw new InvalidEnumArgumentException(
            String.Format("day should be between " +
            "Monday and Friday. The actual value " +
            "is {0}.", day));
    }

    // Do method work
}

一时为了便于,会把参数校验的方法,做贰个通用的帮助类实行拍卖,如在自个儿的公用类Curry面提供了二个:参数验证的通用校验支持类 ArgumentValidation,使用如下代码所示。

     public class TranContext:IDisposable   
     {   
         private readonly TranSetting setting=null;   
         private IBuilder builder=null;   
         private ILog log=null;   
         private ManuSetting section=null;   
         public event EndReportEventHandler EndReport;   
         public TranContext()   
         {   
        }   
        public TranContext(TranSetting setting)   
        {   
            ArgumentValidation.CheckForNullReference (setting,"TranSetting");   
            this.setting =setting;   
        }   
        public TranContext(string key,string askFileName,string operation)   
        {   
            ArgumentValidation.CheckForEmptyString (key,"key");   
            ArgumentValidation.CheckForEmptyString (askFileName,"askFileName");   
            ArgumentValidation.CheckForEmptyString (operation,"operation");   
            setting=new TranSetting (this,key,askFileName,operation);   
        }  

可是这么的章程依旧非常不足完备,相当不够流畅。

2、基于第三方类库的表达措施

在GitHub上有点注脚类库也提供了对参数验证的功能,使用起来拾壹分轻易,选拔大器晚成种流畅的串联写法。如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;
}

public static int[] Multiply(int[] left, int[] right)
{
    Condition.Requires(left, "left").IsNotNull();

    // You can add an optional description to each check
    Condition.Requires(right, "right")
        .IsNotNull()
        .HasLength(left.Length, "left and right should have the same length");

    // Do multiplication
}

这种书写方式比较流利,而且也提供了相比强硬的参数校验格局,除了可以动用其IsNotNull、IsEmpty等内置函数,也足以选择Evaluate那些扩大剖断相当好的函数来拍卖部分自定义的决断,应该说能够满意绝大许多的参数验证须求了,独一不佳的就是内需运用这么些第三方类库吧,偶然候如需扩充就劳动一些。而且貌似的话我们温馨有局地公用类库,假使对参数验证也还需求引进三个类库,如故相比较艰巨一些的(个人见解卡塔 尔(阿拉伯语:قطر‎

 

3、Code Contract

Code Contracts 是微软研商院开采的多少个编制程序类库,笔者最先见到是在C# In Depth 的第二版中,那时.NET 4.0还不曾出来,那时候是当作叁个第三方类仓库储存在的,到了.NET 4.0过后,已经参预到了.NET BCL中,该类存在于System.Diagnostics.Contracts 这几个命名空间中。

那些是美其名曰:协议编制程序

 C#代码协议起点于微软支付的一门研讨语言Spec#(参见

    • 协议工具:满含:ccrewrite(二进制重写器,基于项指标装置确认保证协议得以落到实处进行卡塔尔、ccrefgen(它生成协议引用集,为客商端提供公约音信卡塔 尔(英语:State of Qatar)、cccheck(静态检查器,确定保证代码能在编写翻译时知足供给,实际不是只是检查在实践时实际会时有产生哪些卡塔 尔(英语:State of Qatar)、ccdocgen(它可感到代码中钦点的合同生成xml文书档案卡塔 尔(英语:State of Qatar)。

    • 协议种类:后置条件、前置条件、固定条件、断言和倘诺、旧式左券。

      • 代码协议工具下载及安装:下载地址Http://mng.bz/cn2k。(代码合同工具并不含有在Visual Studio 20第10中学,不过其主干类型位于mscorlib内。卡塔尔

    • 命名空间:System.Diagnostics.Contracts.Contract

Code Contract 使得.NET 中合同式设计和编制程序变得特别轻巧,Contract中的那一个静态方法方法包蕴

  1. Requires:函数入口处必须满意的法则
  2. Ensures:函数出口处必须满意的尺度
  3. Invariants:全数成员函数出口处都必需满意的尺度
  4. Assertions:在某一点必需满意的标准
  5. Assumptions:在某一点一定满意的规格,用来压缩无需的警报消息

Code Contract 的使用文书档案您能够从官方网站下载到。为了方便使用Visual Studio开垦。大家能够设置多个Code Contracts for .NET 插件。安装完了后头,点击Visual Studio中的项目性质,能够见见如下丰盛的采纳项:

365bet体育官网 1

Contract和Debug.Assert有些地点常常:

  1. 都提供了运营时支持:这几个Contracts都以可以被周转的,並且只要条件不被满足,会弹出相仿Assert的同等的对话框报错,如下:
  2. 都得以在恣意的在代码中关闭展开。

然则Contract有越多和越来越强有力的效应:

  1. Contracts的意图尤其清楚,通过不相同的Requires/Ensures等等调用,代表区别种类的基准,比单纯的Assert更易于掌握和进行活动解析
  2. Contracts的地点特别统意气风发,将3种区别口径都位于代码的领头处,而非散见在函数的初步和末段,便于搜索和解析。
  3. 昨今分化的开拓人士、不相同的小组、差异的商店、差异的库大概都会有友好的Assert,那就大大扩充了电动解析的难度,也不便利开拓人士编写代码。而Contracts间接被.NET 4.0支撑,是联合的。
  4. 它提供了静态解析援救,这些我们得以因而计划面板见到,通过静态分析Contracts,静态深入分析工具得以比较简单明白函数的种种有关音信,以至能够用作速龙lisense

Contract中带有了八个工具:

  • ccrewrite, 通过向程序聚集些如二进制数据,来支撑运营时检验
  • cccheck, 运维时检查评定
  • ccdoc, 将Contract自动生成XML文书档案

内置条件的拍卖,如代码所示。

       /// <summary>
        /// 实现“前置条件”的代码契约
        /// </summary>
        /// <param name="text">Input</param>
        /// <returns>Output</returns>
        public static int CountWhiteSpace(string text)
        {
            // 命名空间:using System.Diagnostics.Contracts;
            Contract.Requires<ArgumentNullException>(text != null, "Paramter:text");// 使用了泛型形式的Requires
            return text.Count(char.IsWhiteSpace);
        }

后置条件(postcondition):表示对章程输出的节制:再次来到值、out或ref参数的值,以致此外被改正的气象。Ensures();

        /// <summary>
        /// 实现“后置条件”的代码契约
        /// </summary>
        /// <param name="text">Input</param>
        /// <returns>Output</returns>
        public static int CountWhiteSpace(string text)
        {
            // 命名空间:using System.Diagnostics.Contracts;
            Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(text), "text"); // 使用了泛型形式的Requires
            Contract.Ensures(Contract.Result<int>() > 0); // 1.方法在return之前,所有的契约都要在真正执行方法之前(Assert和Assume除外,下面会介绍)。
                                                          // 2.实际上Result<int>()仅仅是编译器知道的”占位符“:在使用的时候工具知道它代表了”我们将得到那个返回值“。
            return text.Count(char.IsWhiteSpace);
        }

        public static bool TryParsePreserveValue(string text, ref int value)
        {
            Contract.Ensures(Contract.Result<bool>() || Contract.OldValue(value) == Contract.ValueAtReturn(out value)); // 此结果表达式是无法证明真伪的。
            return int.TryParse(text, out value); // 所以此处在编译前就会提示错误信息:Code Contract:ensures unproven: XXXXX
        }

以此代码左券成效相比较强硬,不过好像对于简易的参数校验,引进这么叁个实物认为麻烦,也不见开采职员用的有多大范围,何况还供给超前设置八个工具:Code Contracts for .NET。

从而作者也不匡助于采纳这么些插件的事物,因为代码要交给客商接纳,须求顾客安装多个插件,何况打开相关的代码左券设置,依然相比较辛苦,若无打开,也不会报告客商代码编写翻译出错,只是会在运作的时候不校验方法参数。

 

4、使用内置的公用类库管理

365bet体育官网,基于CuttingEdge.Conditions 的措施,其实大家也得以做二个好像这样的通畅性写法的校验管理,并且没有必要那么麻烦引进第三方类库。

诸如我们在公用类Curry面扩张三个类库,如下代码所示。

    /// <summary>
    /// 参数验证帮助类,使用扩展函数实现
    /// </summary>
    /// <example>
    /// eg:
    /// ArgumentCheck.Begin().NotNull(sourceArray, "需要操作的数组").NotNull(addArray, "被添加的数组");
    /// </example>
    public static class ArgumentCheck
    {
        #region Methods

        /// <summary>
        /// 验证初始化
        /// <para>
        /// eg:
        /// ArgumentCheck.Begin().NotNull(sourceArray, "需要操作的数组").NotNull(addArray, "被添加的数组");
        /// </para>
        /// <para>
        /// ArgumentCheck.Begin().NotNullOrEmpty(tableName, "表名").NotNullOrEmpty(primaryKey, "主键");</para>
        /// <para>
        /// ArgumentCheck.Begin().CheckLessThan(percent, "百分比", 100, true);</para>
        /// <para>
        /// ArgumentCheck.Begin().CheckGreaterThan&lt;int&gt;(pageIndex, "页索引", 0, false).CheckGreaterThan&lt;int&gt;(pageSize, "页大小", 0, false);</para>
        /// <para>
        /// ArgumentCheck.Begin().NotNullOrEmpty(filepath, "文件路径").IsFilePath(filepath).NotNullOrEmpty(regexString, "正则表达式");</para>
        /// <para>
        /// ArgumentCheck.Begin().NotNullOrEmpty(libFilePath, "非托管DLL路径").IsFilePath(libFilePath).CheckFileExists(libFilePath);</para>
        /// <para>
        /// ArgumentCheck.Begin().InRange(brightnessValue, 0, 100, "图片亮度值");</para>
        /// <para>
        /// ArgumentCheck.Begin().Check&lt;ArgumentNullException&gt;(() => config.HasFile, "config文件不存在。");</para>
        /// <para>
        /// ArgumentCheck.Begin().NotNull(serialPort, "串口").Check&lt;ArgumentException&gt;(() => serialPort.IsOpen, "串口尚未打开!").NotNull(data, "串口发送数据");
        /// </para>
        /// </summary>
        /// <returns>Validation对象</returns>
        public static Validation Begin()
        {
            return null;
        }

        /// <summary>
        /// 需要验证的正则表达式
        /// </summary>
        /// <param name="validation">Validation</param>
        /// <param name="checkFactory">委托</param>
        /// <param name="argumentName">参数名称</param>
        /// <returns>Validation对象</returns>
        public static Validation Check(this Validation validation, Func<bool> checkFactory, string argumentName)
        {
            return Check<ArgumentException>(validation, checkFactory, string.Format(Resource.ParameterCheck_Match2, argumentName));
        }

        /// <summary>
        /// 自定义参数检查
        /// </summary>
        /// <typeparam name="TException">泛型</typeparam>
        /// <param name="validation">Validation</param>
        /// <param name="checkedFactory">委托</param>
        /// <param name="message">自定义错误消息</param>
        /// <returns>Validation对象</returns>
        public static Validation Check<TException>(this Validation validation, Func<bool> checkedFactory, string message)
        where TException : Exception
        {
            if(checkedFactory())
            {
                return validation ?? new Validation()
                {
                    IsValid = true
                };
            }
            else
            {
                TException _exception = (TException)Activator.CreateInstance(typeof(TException), message);
                throw _exception;
            }
        }
......

上面提供了叁个例行的反省和泛型类型检查的通用方法,大家只要急需对参数检查,如下代码所示。

ArgumentCheck.Begin().NotNull(sourceArray, "需要操作的数组").NotNull(addArray, "被添加的数组");

而以此NotNull就是我们依照下面的定义方法进行扩展的函数,如下代码所示。

        /// <summary>
        /// 验证非空
        /// </summary>
        /// <param name="validation">Validation</param>
        /// <param name="data">输入项</param>
        /// <param name="argumentName">参数名称</param>
        /// <returns>Validation对象</returns>
        public static Validation NotNull(this Validation validation, object data, string argumentName)
        {
            return Check<ArgumentNullException>(validation, () => (data != null), string.Format(Resource.ParameterCheck_NotNull, argumentName));
        }

豆蔻年华致道理我们能够扩大越来越多的自定义检查措施,如引进正则表明式的管理。

ArgumentCheck.Begin().NotNullOrEmpty(libFilePath, "非托管DLL路径").IsFilePath(libFilePath).CheckFileExists(libFilePath);

它的恢宏函数如下所示。

        /// <summary>
        /// 是否是文件路径
        /// </summary>
        /// <param name="validation">Validation</param>
        /// <param name="data">路径</param>
        /// <returns>Validation对象</returns>
        public static Validation IsFilePath(this Validation validation, string data)
        {
            return Check<ArgumentException>(validation, () => ValidateUtil.IsFilePath(data), string.Format(Resource.ParameterCheck_IsFilePath, data));
        }

        /// <summary>
        /// 检查指定路径的文件必须存在,否则抛出<see cref="FileNotFoundException"/>异常。
        /// </summary>
        /// <param name="validation">Validation</param>
        /// <param name="filePath">文件路径</param>
        /// <exception cref="ArgumentNullException">当文件路径为null时</exception>
        /// <exception cref="FileNotFoundException">当文件路径不存在时</exception>
        /// <returns>Validation对象</returns>
        public static Validation CheckFileExists(this Validation validation, string filePath)
        {
            return Check<FileNotFoundException>(validation, () => File.Exists(filePath), string.Format(Resource.ParameterCheck_FileNotExists, filePath));
        }

咱俩能够根据大家的正则表明式校验,封装越多的函数实行连忙利用,固然要自定义的校验,那么就动用根底的Chek函数就能够。

365bet体育官网 2

测量试验下代码应用,如下所示。

        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        [STAThread]
        static void Main(string[] args)
        {
            ArgumentCheck.Begin().NotNull(args, "启动参数");
            string test = null;
            ArgumentCheck.Begin().NotNull(test, "测试参数").NotEqual(test, "abc", "test");

以此ArgumentCheck作为公用类库的三个类,因而使用起来无需重新引进第三方类库,也能够实现符合规律化的校验管理,甚至能够扩大自定义的参数校验,同期也是支撑流式的书写形式,非常方便。 

本文由365bet体育官网发布于365bet体育官网,转载请注明出处:使用优雅方式对参数验证进行处理

关键词:

上一篇:没有了
下一篇:没有了