DotNet-Advanced-Series-1-9-Expression


主要内容概要

1 什么是表达式目录树Expression
2 动态拼装Expression
3 基于Expression扩展应用
4 ExpressionVisitor解析表达式目录树
5 解析Expression生成Sql
6 Expression扩展拼装链接

表达式目录树Expression

先看一下在linq中的两个例子:

new List<int>().Where(a => a > 10); //Linq To Object 
new List<People>().AsQueryable().Where(a => a.Id > 10 && a.Name.ToString().Contains("Hyl")); //Linq To Sql

对比一下:
Func<int, int, int> func = (m, n) => m * n + 2;lambda实例化委托 匿名方法;
Expression<Func<int, int, int>> exp = (m, n) => m * n + 2;Expresssion,这里面的泛型委托就是Func<int, int, int>;
结合下面这个图看一下这里面的概念:
Expression
声明一个表达式目录树;
快捷方式的声明 数据结构;
类似于一个树形结构,描述不同变量和常量之间的关系 数据结构;
表达式目录树:语法树,或者说是一种数据结构;
PS: 如果在调试的时候需要看表达式的具体内容,可以通过这个工具ExpressionTreeVisualizer
另外,Expression中不能有语句体,即只能有一行,不能有多行。

表达式目录树变成委托
通过Compile()方法可以将目录树变成委托:

int iResult1 = func.Invoke(12, 23);
//exp.Compile()=> 委托; 
int iResult2 = exp.Compile().Invoke(12, 23); //12 * 23 +2  278

手动拼装表达式目录树

  1. 首先来一个简答一点的拼装,就实现123+234这个加法:
//Expression<Func<int>> expression = () => 123 + 234; 
ConstantExpression left = Expression.Constant(123, typeof(int));
ConstantExpression right = Expression.Constant(234, typeof(int));
var plus = Expression.Add(left, right);
Expression<Func<int>> expression = Expression.Lambda<Func<int>>(plus, new ParameterExpression[0]);
int iResult = expression.Compile().Invoke();
int iResult1 = expression.Compile()();

其中,这个Expression.Lambda方法是这样的public static Expression<TDelegate> Lambda<TDelegate>(Expression body,params ParameterExpression[] parameters),返回一个泛型的表达式目录树。

  1. 然后再来个稍微复杂点的表达式目录树拼装例子:
//Expression<Func<int, int, int>> expression = (m, n) => m * n + m + n + 2; 
ParameterExpression m = Expression.Parameter(typeof(int), "m");
ParameterExpression n = Expression.Parameter(typeof(int), "n");
ConstantExpression constant2 = Expression.Constant(2, typeof(int));
var multiply = Expression.Multiply(m, n);
var plus1 = Expression.Add(multiply, m);
var plus2 = Expression.Add(plus1, n);
var plus = Expression.Add(plus2, constant2);
Expression<Func<int, int, int>> expression = Expression.Lambda<Func<int, int, int>>(plus, new ParameterExpression[]
{
  m,
  n
});
int iResult = expression.Compile().Invoke(12, 10);
  1. 再来一个不是加减乘除的:
    //   Expression<Func<People, bool>> lambda = (x) => x.Id.ToString().Equals("5"); 
    ParameterExpression x = Expression.Parameter(typeof(People), "x");
    FieldInfo field = typeof(People).GetField("Id");
    ConstantExpression constant5 = Expression.Constant("5");
    var fieldExp = Expression.Field(x, field);
    MethodInfo toString = typeof(int).GetMethod("ToString", new Type[] { });
    MethodInfo equals = typeof(string).GetMethod("Equals", new Type[] { typeof(string) });
    var tostringExp = Expression.Call(fieldExp, toString, new Expression[0]);
    var equalsExp = Expression.Call(tostringExp, equals, new Expression[]
    {
    constant5
    });
    Expression<Func<People, bool>> expression = Expression.Lambda<Func<People, bool>>(equalsExp, new ParameterExpression[]
    {
    x
    });
    bool bResult = expression.Compile().Invoke(new People()
    {
    Id = 5,
    Name = "德玛西亚"
    });
    整个过程就是按照Lambda表达式的内容来写的x.Id.ToString().Equals("5");。其中,在找方法的时候要添加Type选择无参的:GetMethod("ToString", new Type[] { })

动态

有一个场景是:以前根据用户输入拼装条件,用户输入名称,为空就跳过:

  1. 第一个时代:
    通过sql语句来拼接。
string sql = "SELECT * FROM USER WHERE 1=1";
Console.WriteLine("用户输入个名称,为空就跳过");
string name = Console.ReadLine();
if (!string.IsNullOrWhiteSpace(name))
{
  sql += $" and name like '%{name}%'";
}

Console.WriteLine("用户输入个账号,为空就跳过");
string account = Console.ReadLine();
if (!string.IsNullOrWhiteSpace(account))
{
  sql += $" and account like '%{account}%'";
}
  1. 第二个时代:

界面上有好几处输入,多来几个条件就会没法写。
出现整个DBSet 暴露出来了,在使用的时候是非常危险。

//Linq To Sql
var DbSet = new List<People>().AsQueryable();//然后对表操作
//Expression<Func<People, bool>> exp = a => a.Name.Contains("Richard") && a.Id == 10; 
Expression<Func<People, bool>> exp = null;
Console.WriteLine("用户输入个名称,为空就跳过");
string name = Console.ReadLine();
if (!string.IsNullOrWhiteSpace(name))
{
  //exp = a => a.Name.Contains(name); 
  //DbSet = DbSet.Where(a => a.Name.Contains(name));
}
Console.WriteLine("用户输入个账号,为空就跳过");
string account = Console.ReadLine();
if (!string.IsNullOrWhiteSpace(account))
{
//exp = a => a.Account.Contains(account);
//DbSet = DbSet.Where(a => a.Account.Contains(account));// 出现整个DBSet 暴露出来了,在使用的时候是非常危险
}
exp = a => a.Account.Contains(account) && a.Name.Contains(name);

有两个解决方案:

  • 以前有个表达式树的扩展,扩展了and 和 or,基于visitor实现
  • 根据字符串和条件自动拼装出来
Expression<Func<People, bool>> exp1 = item => item.Account.Contains("Admin") && item.Name.Contains("Richard");

if (Account 不为空)  
var Account = typeof(People).GetProperty("Account");
var contains = typeof(string).GetMethod("Contains");
ParameterExpression a = Expression.Parameter(typeof(People), "a");
Expression<Func<People, bool>> expression = Expression.Lambda<Func<People, bool>>(Expression.AndAlso(Expression.Call(Expression.Property(a, Account, (MethodInfo)MethodBase.GetMethodFromHandle(ldtoken(Contains())), new Expression[]
{
  Expression.Constant("Admin", typeof(string))
}), Expression.Call(Expression.Property(a, (MethodInfo)MethodBase.GetMethodFromHandle(ldtoken(get_Name()))), (MethodInfo)MethodBase.GetMethodFromHandle(ldtoken(Contains())), new Expression[]
{
  Expression.Constant("Richard", typeof(string))})), new ParameterExpression[]
{
  a
});

这个过程可以根据用户输入,封装成一个表达式目录树的自动生成。

下面再看一个关于实体类和实体类DTO的例子,现有两个类People和PeopleCopy:

//Entity
public class People
{
  public int Age { get; set; }
  public string Name { get; set; }
  public int Id;
}

// PeopleDTO
public class PeopleCopy
{ 
  public int Age { get; set; }
  public string Name { get; set; }
  public int Id;
}

在做转换的时候我们是不可以强制转换的,要么是逐个赋值来进行转换的:

People people = new People()
{
  Id = 11,
  Name = "Richard",
  Age = 31
};

PeopleCopy peopleCopy = new PeopleCopy()//硬编码 //硬编码性能好,但是通用型差
{
  Id = people.Id,
  Name = people.Name,
  Age = people.Age
};

思考:如果说有其他别的类型需要转换,那么不是为所有的类型都需要写这样代码?
方法一: 反射+泛型方法

public class ReflectionMapper
{
  public static TOut Trans<TIn, TOut>(TIn tIn)
  {
    TOut tOut = Activator.CreateInstance<TOut>();
    foreach (var itemOut in tOut.GetType().GetProperties())
    {
      var propIn = tIn.GetType().GetProperty(itemOut.Name);
      itemOut.SetValue(tOut, propIn.GetValue(tIn)); 
    }
    foreach (var itemOut in tOut.GetType().GetFields())
    {
      var fieldIn = tIn.GetType().GetField(itemOut.Name);
      itemOut.SetValue(tOut, fieldIn.GetValue(tIn)); 
    }
    return tOut;
  }
}

通过上述这个泛型反射的方法,可以把目标DTO的所有属性和字段在实体中找到并赋值,完成实体到DTO的的转换。

PeopleCopy peopleCopy1 = ReflectionMapper.Trans<People, PeopleCopy>(people);

方法二:序列化反序列化方式

public class SerializeMapper
{
  public static TOut Trans<TIn, TOut>(TIn tIn)
  {
    return JsonConvert.DeserializeObject<TOut>(JsonConvert.SerializeObject(tIn));
  }
}

通过将实体对象people序列化得到一个json,然后再反序列化成peoplecopy。

PeopleCopy peopleCopy2 = SerializeMapper.Trans<People, PeopleCopy>(people);

上面两个方法本质上都是通过反射来实现,在性能上肯定无法保证。那有没有什么方法既能保证通用又能保证性能呢?

Func<People, PeopleCopy> exp1 = p =>
{
  return new PeopleCopy()
  {
    Id = p.Id,
    Name = p.Name,
    Age = p.Age
  };
};

PeopleCopy peopleCopy3=func.Invoke(people);

上述过程提供了一个思路:想办法去动态拼装这个委托,然后缓存下委托,后面再次转换时就没有性能损耗了。

方法三: 泛型方法+表达式目录 = 既可以通用,效率高

public class ExpressionMapper
{
  // 字典缓存--hash分布  
  private static Dictionary<string, object> _Dic = new Dictionary<string, object>();  //超过一定的数量之后  在字典中获取值就会有性能损耗

  public static TOut Trans<TIn, TOut>(TIn tIn)
  {
    string key = string.Format("funckey_{0}_{1}", typeof(TIn).FullName, typeof(TOut).FullName);
    if (!_Dic.ContainsKey(key))
    {
      //动态的生成表达式
      ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p");
      List<MemberBinding> memberBindingList = new List<MemberBinding>();
      foreach (var item in typeof(TOut).GetProperties())
      {
        MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name));
        MemberBinding memberBinding = Expression.Bind(item, property);
        memberBindingList.Add(memberBinding);
      }
      foreach (var item in typeof(TOut).GetFields())
      {
        MemberExpression property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name));
        MemberBinding memberBinding = Expression.Bind(item, property);
        memberBindingList.Add(memberBinding);
      }
      MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray());
      Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[]
      {
        parameterExpression
      });
      Func<TIn, TOut> func = lambda.Compile();//拼装是一次性的
      _Dic[key] = func;
    }
    return ((Func<TIn, TOut>)_Dic[key]).Invoke(tIn);
  }
}

通过动态地拼装委托,可以把表达式缓存起来,提高性能。但是字典缓存的方式是通过hash分布来寻址,当查找次数比较大的时候,会有性能问题。

方法四:泛型缓存+表达式目录

public class ExpressionGenericMapper<TIn, TOut>//Mapper`2 //正对于每两个不同类型的组合都会生成副本
{
  private static Func<TIn, TOut> _FUNC = null;// 在每个副本中都有一个委托
  static ExpressionGenericMapper() //静态构造函数 生成表达式目录树 
  {
    ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p");
    List<MemberBinding> memberBindingList = new List<MemberBinding>();
    foreach (var item in typeof(TOut).GetProperties())
    {
      MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name));
      MemberBinding memberBinding = Expression.Bind(item, property);
      memberBindingList.Add(memberBinding);
    }
    foreach (var item in typeof(TOut).GetFields())
    {
      MemberExpression property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name));
      MemberBinding memberBinding = Expression.Bind(item, property);
      memberBindingList.Add(memberBinding);
    }
    MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)),memberBindingList.ToArray());
    Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[]
    {
      parameterExpression
    });
    _FUNC = lambda.Compile();//拼装是一次性的   转换成委托以后放入副本的静态变量中去
  }
  public static TOut Trans(TIn t) // 直接获取副本的静态变量(委托)
  {
    return _FUNC(t);
  }
}

泛型缓存的优点在第一小节已经演示过了,下面可以做个试验来验证上述四种方式的性能:

Console.WriteLine("****************************性能测试结果***************************");
People people = new People()
{
  Id = 11,
  Name = "Richard",
  Age = 31
};
long common = 0;
long generic = 0;
long cache = 0;
long reflection = 0;
long serialize = 0;
{
  Stopwatch watch = new Stopwatch();
  watch.Start();
  for (int i = 0; i < 100_000; i++)
  {
  PeopleCopy peopleCopy = new PeopleCopy()
  {
    Id = people.Id,
    Name = people.Name,
    Age = people.Age
  };
  }
  watch.Stop();
  common = watch.ElapsedMilliseconds;
}
{
  Stopwatch watch = new Stopwatch();
  watch.Start();
  for (int i = 0; i < 100_000; i++)
  {
    PeopleCopy peopleCopy = ReflectionMapper.Trans<People, PeopleCopy>(people);
  }
  watch.Stop();
  reflection = watch.ElapsedMilliseconds;
}
{
  Stopwatch watch = new Stopwatch();
  watch.Start();
  for (int i = 0; i < 100_000; i++)
  {
    PeopleCopy peopleCopy = SerializeMapper.Trans<People, PeopleCopy>(people);
  }
  watch.Stop();
  serialize = watch.ElapsedMilliseconds;
}
{
  Stopwatch watch = new Stopwatch();
  watch.Start();
  for (int i = 0; i < 100_000; i++)
  {
    PeopleCopy peopleCopy = ExpressionMapper.Trans<People, PeopleCopy>(people);
  }
  watch.Stop();
  cache = watch.ElapsedMilliseconds;
}
{
  Stopwatch watch = new Stopwatch();
  watch.Start();
  for (int i = 0; i < 100_000; i++)
  {
    PeopleCopy peopleCopy = ExpressionGenericMapper<People, PeopleCopy>.Trans(people);
  }
  watch.Stop();
  generic = watch.ElapsedMilliseconds;
} 
Console.WriteLine($"common = { common} ms");
Console.WriteLine($"reflection = { reflection} ms");
Console.WriteLine($"serialize = { serialize} ms");
Console.WriteLine($"cache = { cache} ms");
Console.WriteLine($"generic = { generic} ms");

结果显示:硬编码方法是27ms,反射方法是2195ms,序列化方法是3589ms,字典缓存方法是556ms,泛型缓存是91ms。

小结一下:

  • 通过拼接表达式目录树+ 泛型缓存性能最高!
  • 硬编码性能最高,为了通用,动态生成硬编码(表达式目录树拼装)==最完美

以上内容都是围绕表达式目录树的拼装来展开,下半部分是围绕表达式目录树的解析来展开的。
首先我们来想个问题:为什么在封装SQL查询的时候,各种查询条件没有具体实现呢,一般只有一个Id呢?因为写不出来。
属性可能是ID Name Accound State等;值也可能是int string datetime等类型;操作也可能是大于 小于 等于包含等;那么有可能是一个条件,两个条件,或者说N个条件。是没法写出来通用的封装方法的。
值得注意的是,这里我们不考虑传递sql语句,例如id=3这种。
如果非要解决这个问题,可以用个土办法,就是把需要的内容封装成一个对象,例如 colum operation value,然后接受一个集合,再去解析这个集合,执行操作,这是所谓土办法。
那么表达式目录树的出现就很好的解决了这个问题,它定义了一种数据结构;调用方会有各种的条件需要传递下去,底层需要解析调用传递的东西,所以需要一个数据结构(语法/约定),上端去组装,下端去解析。

再次回到刚才的问题,假设有public List<T> FindWhere<T>()需要传递一些规则进去,就可以通过表达式目录树来实现:public List<T> FindWhere<T>(Expression<Func<T,bool>>expression),例如这里expression=x=>x.Id<5&& x.Age>5去做约束。

那么问题来了:下是如何去解析表达式目录树呢?

ExpressionVisitor解析表达式目录树

ExpressionVisitor访问者类,Visit是一个入口,先判断,进一步的解析,然后

  1. lambada会区分参数和方法体,调度到更加专业的方法中解析;
  2. 根据表达式的类型,调度到更加专业的方法中解析;
  3. 默认根据旧的模式产生一个新的表达式目录树;也可以自行扩展,把一些解读操作进行变化。
  4. 表达式目录树是一个二叉树,visit做的事就是一层层地解析下去,一直到最终的叶节点。

还是回到这个图,现在是看如何解析这颗树,从根节点往下拆。
Expression
首先有个内置的ExpressionVisitor这个类表示表达式树的访问者或重写者。

public class OperationsVisitor : ExpressionVisitor
{
  public Expression Modify(Expression expression)
  {
    return this.Visit(expression);
  }
  public override Expression Visit(Expression node)
  {
    Console.WriteLine($"Visit {node.ToString()}");
    return base.Visit(node);
  }
  protected override Expression VisitBinary(BinaryExpression b)
  {
    Console.WriteLine(b.ToString());
    if (b.NodeType == ExpressionType.Add)
    {
      Expression left = this.Visit(b.Left); // 会产生一个新的表达式目录树
      Expression right = this.Visit(b.Right); 
      Console.WriteLine(left.ToString());
      Console.WriteLine(right.ToString());
      return Expression.Subtract(left, right);
    }
    else if (b.NodeType==ExpressionType.Multiply)
    {
      Expression left = this.Visit(b.Left); // 会产生一个新的表达式目录树
      Expression right = this.Visit(b.Right);
      return Expression.Divide(left, right);
    }
    var express = base.VisitBinary(b);//默认的二元访问,其实什么都不干
    return express;  
  }

  protected override Expression VisitConstant(ConstantExpression node)
  {
    Console.WriteLine(node.ToString());
    return base.VisitConstant(node);
  }
}

然后具体使用:

//修改表达式目录树
Expression<Func<int, int, int>> exp = (m, n) => m * n + 2;
OperationsVisitor visitor = new OperationsVisitor();
//visitor.Visit(exp);
Expression expNew = visitor.Modify(exp);

分析一下这个过程:Visit是个入口,解读node表达式
根据表达式的类型,将表达式调度到此类中更专用的访问方法之一的表达式。
在本例中,读到是mn+2是一个二元表达式,base.Visit()方法就会调用这个VisitBinary()方法。
本例的扩展可以检测二元的操作如果是相加就改成相减,如果是相乘就改成相除。
接着调用this.Visit()这个方法,继续调用base.visit()这个方法,即继续判断Expression的类型,是否是二元运算式,如果是的话,还会走到VisitBinary这个方法中,继续改掉运算符号。
最后再调用base.VisitBinary()这个默认的二元访问,什么的不干。
这样一来最后`m
n+2变成了m*n-2`这个表达式了。
Visit本身不做什么,就是去递归遍历,我们可以通过override在访问的时候去修改一些内容。这里的例子只是为了演示访问的过程,为下面的扩展应用做一些铺垫。

解析Expression生成Sql

思考一下:Visit的意义是什么?应用场景在哪?
写成这样的表达式目录树怎么变成sql呢?
Expression<Func<People, bool>> lambda = x => x.Age > 5;
select * from People where Age>5
new List<People>().AsQueryable().Where(x => x.Age > 5 && x.Id == 10);
要完成这个sql需要三个要素,一个是age 一个是大于符号 一个是5 还有x是来自于泛型类型的。这就变成了要从表达式目录树里面获取这三要素。换句话说,这里是ORM的具体实现。

ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
vistor.Visit(lambda);
Console.WriteLine(vistor.Condition());

下面就来具体看下这个扩展的ConditionBuilderVisitor的实现:

public class ConditionBuilderVisitor : ExpressionVisitor
{
  private Stack<string> _StringStack = new Stack<string>();
  public string Condition()
  {
    string condition = string.Concat(this._StringStack.ToArray());
    this._StringStack.Clear();
    return condition;
  }
  //((((( [Age]  > 5) AND ( [Id]  > 5)) AND ( [Name]  LIKE '1%')) AND ( [Name]  LIKE '%1')) AND ( [Name]  LIKE '%1%'))

  // 二元表达式
  protected override Expression VisitBinary(BinaryExpression node)
  {
    if (node == null) throw new ArgumentNullException("BinaryExpression");
    this._StringStack.Push(")");
    base.Visit(node.Right);//解析右边
    this._StringStack.Push(" " + node.NodeType.ToSqlOperator() + " ");
    base.Visit(node.Left);//解析左边
    this._StringStack.Push("(");
    return node;
  }

  //变量表达式
  protected override Expression VisitMember(MemberExpression node)
  {
    if (node == null) throw new ArgumentNullException("MemberExpression");
    this._StringStack.Push(" [" + node.Member.Name + "] ");
    return node;
  }

  //常量表达式
  protected override Expression VisitConstant(ConstantExpression node)
  {
    if (node == null) throw new ArgumentNullException("ConstantExpression");
    this._StringStack.Push( node.Value.ToString());
    return node;
  }

  // 方法表达式
  protected override Expression VisitMethodCall(MethodCallExpression m)
  {
    if (m == null) throw new ArgumentNullException("MethodCallExpression");
    string format;
    switch (m.Method.Name)
    {
      case "StartsWith":
          format = "({0} LIKE '{1}%')";
          break;
      case "Contains":
          format = "({0} LIKE '%{1}%')";
          break;
      case "EndsWith":
          format = "({0} LIKE '%{1}')";
          break;
      default:
          throw new NotSupportedException(m.NodeType + " is not supported!");
    }
    this.Visit(m.Object);
    this.Visit(m.Arguments[0]);
    string right = this._StringStack.Pop();
    string left = this._StringStack.Pop();
    this._StringStack.Push(String.Format(format, left, right));
    return m;
  }
}

在遍历表达式目录树的时候顺便把SQL也拼装出来了,ORM就是在做这样的事情。Linq to SQL 的意义就是希望使用者不用关注SQL语句,可以直接从可读性很强的lambda表达式出发。本例中是通过栈来从右往左进行遍历。
这样一来,上述实现可以应对更加复杂的表达式目录树:
例子一:

{
  //select * from People where Age>5 and Id>5
  Expression<Func<People, bool>> lambda = x => x.Age > 5
                                             && x.Id > 5
                                             && x.Name.StartsWith("1")
                                             && x.Name.EndsWith("1")
                                             && x.Name.Contains("1");

  string sql = string.Format("Delete From [{0}] WHERE {1}"
     , typeof(People).Name
     , " [Age]>5 AND [ID] >5"
     );
  ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
  vistor.Visit(lambda);
  Console.WriteLine(vistor.Condition());
}

例子二:

{
  //且或非
  Expression<Func<People, bool>> lambda = x => x.Age > 5 && x.Name == "A" || x.Id > 5;
  ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
  vistor.Visit(lambda);
  Console.WriteLine(vistor.Condition());
}
{
  Expression<Func<People, bool>> lambda = x => x.Age > 5 || (x.Name == "A" && x.Id > 5);
  ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
  vistor.Visit(lambda);
  Console.WriteLine(vistor.Condition());
}
{
  Expression<Func<People, bool>> lambda = x => (x.Age > 5 || x.Name == "A") && x.Id > 5;
  ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
  vistor.Visit(lambda);
  Console.WriteLine(vistor.Condition());
}

表达式链接

Expression<Func<People, bool>> lambda1 = x => x.Age > 5;
Expression<Func<People, bool>> lambda2 = x => x.Id > 5;
Expression<Func<People, bool>> lambda3 = lambda1.And(lambda2);
Expression<Func<People, bool>> lambda4 = lambda1.Or(lambda2);
Expression<Func<People, bool>> lambda5 = lambda1.Not();
Do1(lambda3);
Do1(lambda4);
Do1(lambda5);

private static void Do1(Expression<Func<People, bool>> func)
{
  List<People> people = new List<People>()
  {
    new People(){Id=4,Name="123",Age=4},
    new People(){Id=5,Name="234",Age=5},
    new People(){Id=6,Name="345",Age=6},
  };
  List<People> peopleList = people.Where(func.Compile()).ToList();
}

对于这三个操作,通常是进行扩展的:

  • 合并表达式 expr1 AND expr2
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
  //return expression.lambda<func<t, bool>>(expression.andalso(expr1.body, expr2.body), expr1.parameters);
  //先判断非空 空不能visit 
  ParameterExpression newParameter = Expression.Parameter(typeof(T), "c");
  NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter);
  var left = visitor.Replace(expr1.Body);// 重新生成了一个表达式目录树
  var right = visitor.Replace(expr2.Body);
  var body = Expression.And(left, right);
  return Expression.Lambda<Func<T, bool>>(body, newParameter);
}
  • 合并表达式 expr1 or expr2

    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
    {
    ParameterExpression newParameter = Expression.Parameter(typeof(T), "c");
    NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter);
    
    var left = visitor.Replace(expr1.Body);
    var right = visitor.Replace(expr2.Body);
    var body = Expression.Or(left, right);
    return Expression.Lambda<Func<T, bool>>(body, newParameter);
    }
  • 表达式Not操作

public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expr)
{
  var candidateExpr = expr.Parameters[0];
  var body = Expression.Not(expr.Body);
  return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
}

文章作者: Chaoqiang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Chaoqiang !
评论
 上一篇
DotNet-Advanced-Series-1-10-IOSerialize DotNet-Advanced-Series-1-10-IOSerialize
主要内容概要1 文件夹/文件 检查、新增、复制、移动、删除,递归编程技巧2 文件读写,记录文本日志,读取配置文件3 三种序列化器,xml和json4 验证码、图片缩放 IO文件夹检测和管理配置文件AppSettings:会有一些在开发环境
下一篇 
DotNet-Advanced-Series-1-8-LambdaLinq DotNet-Advanced-Series-1-8-LambdaLinq
主要内容概要1 匿名方法 lambda表达式2 匿名类 var 扩展方法3 linq to object 匿名方法lambda演变历史首先我们定义了如下这么些委托,还有DoNothing和Study方法: public delegate
  目录