只要十步,你就可以应用表达式树来优化动态调用(一)( 五 )

< minLength? ValidateResult.Error($"Length of Name should be great than {minLength}"): ValidateResult.Ok();}}}代码要点:

  1. ValidateCore 方法被拆分为了 ValidateNameRequired 和 ValidateNameMinLength 两个方法 , 分别验证 Name 的 Required 和 MinLength 。
  2. Init 方法中使用了 local function 从而实现了方法 “先使用后定义” 的效果 。 读者可以自上而下阅读 , 从顶层开始了解整个方法的逻辑 。
  3. Init 整体的逻辑就是通过表达式将 ValidateNameRequired 和 ValidateNameMinLength 重新组合成一个形如 ValidateCore 的委托 Func
  4. Expression.Parameter 用于标明委托表达式的参数部分 。
  5. Expression.Variable 用于标明一个变量 , 就是一个普通的变量 。 类似于代码中的 var a 。
  6. Expression.Label 用于标明一个特定的位置 。 在该样例中 , 主要用于标定 return 语句的位置 。 熟悉 goto 语法的开发者知道 ,goto 的时候需要使用 label 来标记想要 goto 的地方 。 而实际上 , return 就是一种特殊的 goto 。 所以想要在多个语句块中 return 也同样需要标记后才能 return 。
  7. Expression.Block 可以将多个表达式顺序组合在一起 。 可以理解为按顺序写代码 。 这里我们将 CreateDefaultResult、CreateValidateNameRequiredExpression、CreateValidateNameMinLengthExpression 和 Label 表达式组合在一起 。 效果就类似于把这些代码按顺序拼接在一起 。
  8. CreateValidateNameRequiredExpression 和 CreateValidateNameMinLengthExpression 的结构非常类似 , 因为想要生成的结果表达式非常类似 。
  9. 不必太在意 CreateValidateNameRequiredExpression 和 CreateValidateNameMinLengthExpression 当中的细节 。 可以在本样例全部阅读完之后再尝试了解更多的 Expression.XXX 方法 。
  10. 经过这样的修改之后 , 我们就实现了扩展 。 假设现在需要对 Name 增加一个 MaxLength 不得超过 16 的验证 。 只需要增加一个 ValidateNameMaxLength 的静态方法 , 添加一个 CreateValidateNameMaxLengthExpression 的方法 , 并且加入到 Block 中即可 。 读者可以尝试动手操作一波实现这个效果 。
第三步 , 读取属性我们来改造 ValidateNameRequired 和 ValidateNameMinLength 两个方法 。 因为现在这两个方法接收的是 CreateClaptrapInput 作为参数 , 内部的逻辑也被写死为验证 Name , 这很不优秀 。
我们将改造这两个方法 , 使其传入 string name 表示验证的属性名称 , string value 表示验证的属性值 。 这样我们就可以将这两个验证方法用于不限于 Name 的更多属性 。
using System;using System.ComponentModel.DataAnnotations;using System.Diagnostics;using System.Linq.Expressions;using FluentAssertions;using NUnit.Framework;// ReSharper disable InvalidXmlDocCommentnamespace Newbe.ExpressionsTests{////// Property Expression///public class X03PropertyValidationTest03{private const int Count = 10_000;private static Func _func;[SetUp]public void Init(){try{var finalExpression = CreateCore();_func = finalExpression.Compile();Expression> CreateCore(){// exp for inputvar inputExp = Expression.Parameter(typeof(CreateClaptrapInput), "input");var nameProp = typeof(CreateClaptrapInput).GetProperty(nameof(CreateClaptrapInput.Name));Debug.Assert(nameProp != null, nameof(nameProp) + " != null");var namePropExp = Expression.Property(inputExp, nameProp);var nameNameExp = Expression.Constant(nameProp.Name);var minLengthPExp = Expression.Parameter(typeof(int), "minLength");// exp for outputvar resultExp = Expression.Variable(typeof(ValidateResult), "result");// exp for return statementvar returnLabel = Expression.Label(typeof(ValidateResult));// build whole blockvar body = Expression.Block(new[] {resultExp},CreateDefaultResult(),CreateValidateNameRequiredExpression(),CreateValidateNameMinLengthExpression(),Expression.Label(returnLabel, resultExp));// build lambda from bodyvar final = Expression.Lambda