C# 表达式树学习

2016年11月13日 12:27 · 阅读(1022) ·

[目录]

参考文章

C#之Expression表达式树实例

C# 知识回顾 - 表达式树 Expression Trees

表达式树的解析

打造自己的LINQ Provider(上):Expression Tree揭秘

Lambda表达式和表达式树

表达式树的定义

表达式树也称表达式目录树,将代码以一种抽象的方式表示成一个对象树,树中每个节点本身都是一个表达式。表达式树不是可执行代码,它是一种数据结构

  1. #region 程序集 System.Core.dll, v4.0.30319
  2. // C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Profile\Client\System.Core.dll
  3. #endregion
  4. using System.Collections.Generic;
  5. using System.Runtime.CompilerServices;
  6. namespace System.Linq.Expressions
  7. {
  8. // 摘要:
  9. // 以表达式树的形式将强类型 lambda 表达式表示为数据结构。此类不能被继承。
  10. //
  11. // 类型参数:
  12. // TDelegate:
  13. // System.Linq.Expressions.Expression<TDelegate> 表示的委托的类型。
  14. public sealed class Expression<TDelegate> : LambdaExpression
  15. {
  16. protected internal override Expression Accept(ExpressionVisitor visitor);
  17. //
  18. // 摘要:
  19. // 将表达式树描述的 lambda 表达式编译为可执行代码,并生成表示该 lambda 表达式的委托。
  20. //
  21. // 返回结果:
  22. // 一个 TDelegate 类型的委托,它表示由 System.Linq.Expressions.Expression<TDelegate> 描述的已编译的
  23. // lambda 表达式。
  24. public TDelegate Compile();
  25. //
  26. // 摘要:
  27. // 生成表示 lambda 表达式的委托。
  28. //
  29. // 参数:
  30. // debugInfoGenerator:
  31. // 编译器用于标记序列点并批注局部变量的调试信息生成器。
  32. //
  33. // 返回结果:
  34. // 包含 lambda 的已编译版本的委托。
  35. public TDelegate Compile(DebugInfoGenerator debugInfoGenerator);
  36. //
  37. // 摘要:
  38. // 创建一个与此表达式类似的新表达式,但使用所提供的子级。如果所有子级都相同,则将返回此表达式。
  39. //
  40. // 参数:
  41. // body:
  42. // 结果的 System.Linq.Expressions.LambdaExpression.Body 属性。
  43. //
  44. // parameters:
  45. // 结果的 System.Linq.Expressions.LambdaExpression.Parameters 属性。
  46. //
  47. // 返回结果:
  48. // 此表达式(如果未更改任何子级),或带有更新的子级的表达式。
  49. public Expression<TDelegate> Update(Expression body, IEnumerable<ParameterExpression> parameters);
  50. }
  51. }

创建编译表达式树

System.Linq.Expressions 命名空间中包含了代表表达式的各个类,所有类都 Expression 派生,我们可以通过这些类中的静态方法来创建表达式类的实例

Lambda 表达式创建编译表达式树

  1. //定义一个表达式,返回 x+y
  2. Expression<Func<int, int, int>> expr = (x, y) => x + y;
  3. //将表达式树描述的 lambda 表达式编译为可执行代码,并生成表示该 lambda 表达式的委托
  4. int result = expr.Compile()(1, 2);
  5. Console.WriteLine(result);

输出 3

动态创建编译表达式树

  1. //动态构建表达式树
  2. //定义一个表达式参数名称为 x,类型为 int
  3. ParameterExpression x = Expression.Parameter(typeof(int), "x");
  4. //定义一个表达式参数名称为 y,类型为 int
  5. ParameterExpression y = Expression.Parameter(typeof(int), "y");
  6. //这个表达式的主体是 2 个参数相加
  7. var body = Expression.Add(x, y);
  8. //创建一个表达式
  9. var w = Expression.Lambda<Func<int, int, int>>(body, new ParameterExpression[] { x, y });
  10. //将表达式树描述的 lambda 表达式编译为可执行代码,并生成表示该 lambda 表达式的委托
  11. Console.WriteLine(w.Compile()(1, 2));

输出 3

解析表达式树

例子一-表达式树主要属性

  1. //定义一个表达式,返回 x+y
  2. Expression<Func<int, int, int>> expr = (x, y) => x + y;
  3. //开始解析
  4. ParameterExpression pe = expr.Parameters[0];//Lambda 表达式参数
  5. BinaryExpression body = (BinaryExpression)expr.Body;//Lambda 表达式主体 num == 0
  6. Console.WriteLine("解析:{0} => {1} {2} {3}", pe.Name, body.Left, body.NodeType, body.Right);

输出:解析:x => x Add y

表达式树的主要属性有:

名称 含义
Type 代表求值表达式的.NET类型,可以把它视为一个返回类型
Body 表达式主体
Left 左节点
Right 右节点
NodeType 当前节点类型

表达式主体 x + y 是一个二元运算表达式,因此可以将 Body 转换成 BinaryExpression 类型来访问 Left 和 Right。

Left 和 Right 的 NodeType 分别为 MemberAccess(从字段或属性进行读取的运算)、Constant(常量)。

因此可以将 Left 转换成 MemberExpression 类型来访问 Member 属性,将 Right 转换成 ConstantExpression 类型来访问 Value 属性。

再看下面的例子

例子二-解析简单表达式

  1. class TestModel
  2. {
  3. public int Id { get; set; }
  4. public string Name { get; set; }
  5. }
  6. /// <summary>
  7. /// ExpressionType 帮助类
  8. /// </summary>
  9. class ExpressionTypeHelper
  10. {
  11. /// <summary>
  12. /// 根据相关表达式返回对应格式的字符串
  13. /// </summary>
  14. /// <param name="expression">Expression</param>
  15. /// <returns>对应格式的字符串</returns>
  16. public string ResolveExpression(Expression<Func<TestModel, bool>> expression)
  17. {
  18. var bodyNode = (BinaryExpression)expression.Body;
  19. var leftNode = (MemberExpression)bodyNode.Left;
  20. var rightNode = (ConstantExpression)bodyNode.Right;
  21. return string.Format("{0} {2} {1}",leftNode.Member.Name,rightNode.Value,bodyNode.NodeType.TransferExpressionType());
  22. }
  23. }
  24. /// <summary>
  25. /// ExpressionType 扩展类
  26. /// </summary>
  27. static class ExpressionTypeExtend
  28. {
  29. /// <summary>
  30. /// 针对部分 ExpressionType 的一个转换
  31. /// </summary>
  32. /// <param name="expressionType">ExpressionType</param>
  33. /// <returns>返回 ExpressionType 对应的字符串</returns>
  34. public static string TransferExpressionType(this ExpressionType expressionType)
  35. {
  36. string type = "";
  37. switch (expressionType)
  38. {
  39. case ExpressionType.Equal:
  40. type = "=";
  41. break;
  42. case ExpressionType.GreaterThanOrEqual:
  43. type = ">=";
  44. break;
  45. case ExpressionType.LessThanOrEqual:
  46. type = "<=";
  47. break;
  48. case ExpressionType.NotEqual:
  49. type = "!=";
  50. break;
  51. case ExpressionType.AndAlso:
  52. type = "And";
  53. break;
  54. case ExpressionType.OrElse:
  55. type = "Or";
  56. break;
  57. }
  58. return type;
  59. }
  60. }
  61. static void Main(string[] args)
  62. {
  63. Expression<Func<TestModel, bool>> expr = test => test.Name == "luoma";
  64. var ex = new ExpressionTypeHelper();
  65. Console.WriteLine(ex.ResolveExpression(expr));
  66. }

输出:Name = luoma

例子三-解析复杂表达式

实践工作中,会写相对复杂或者说多个条件的表达式。那么再采用上面的方式是无法确认表达式节点的类型进行转换的。我们可以添加一个 Visit 方法,根据 NodeType 转换成对应的 Expression 的类型,从而方法访问对应的属性进行表达式解析。

但是,重写之前,我们得了解一件事,既然叫表达式树,意味着在子节点里,还会有多个节点,如下图

那么,我们假设,只要是 BinaryExpression(二元运算表达式)就会有多个子节,去访问子节点就是一个递归的过程,而终点就是 MemberExpression 和 ConstantExpression,对应字段名称和常量值的拼接

  1. //TestModel 见例子二
  2. //ExpressionTypeExtend 见例子二
  3. /// <summary>
  4. /// ExpressionType 帮助类
  5. /// </summary>
  6. class ExpressionTypeHelper
  7. {
  8. StringBuilder sb = new StringBuilder();
  9. /// <summary>
  10. /// 根据相关表达式返回对应格式的字符串
  11. /// </summary>
  12. /// <param name="expr">Expression</param>
  13. /// <returns>对应格式的字符串</returns>
  14. public string ResolveExpression(Expression<Func<TestModel, bool>> expr)
  15. {
  16. Visit(expr.Body);
  17. return sb.ToString();
  18. }
  19. /// <summary>
  20. /// 根据不同的 NodeType 进行处理
  21. /// </summary>
  22. /// <param name="expr">Expression</param>
  23. public void Visit(Expression expr)
  24. {
  25. switch (expr.NodeType)
  26. {
  27. case ExpressionType.Constant:
  28. VisitConstantExpression(expr);
  29. break;
  30. case ExpressionType.MemberAccess:
  31. VisitMemberExpression(expr);
  32. break;
  33. case ExpressionType.Convert:
  34. VisitUnaryExpression(expr);
  35. break;
  36. default:
  37. VisitBinaryExpression(expr);
  38. break;
  39. }
  40. }
  41. /// <summary>
  42. /// 一元运算符
  43. /// </summary>
  44. /// <param name="expr">表达式</param>
  45. public void VisitUnaryExpression(Expression expr)
  46. {
  47. var e = (UnaryExpression)expr;
  48. Visit(e.Operand);
  49. }
  50. /// <summary>
  51. /// 二元运算符
  52. /// </summary>
  53. /// <param name="expr">表达式</param>
  54. public void VisitBinaryExpression(Expression expr)
  55. {
  56. var e = (BinaryExpression)expr;
  57. sb.Append("(");
  58. Visit(e.Left);
  59. sb.Append(e.NodeType.TransferExpressionType());
  60. Visit(e.Right);
  61. sb.Append(")");
  62. }
  63. /// <summary>
  64. /// 常量值
  65. /// </summary>
  66. /// <param name="expr">表达式</param>
  67. public void VisitConstantExpression(Expression expr)
  68. {
  69. var e = (ConstantExpression)expr;
  70. if (e.Type == typeof(string))
  71. {
  72. sb.Append("'" + e.Value + "'");
  73. }
  74. else
  75. {
  76. sb.Append(e.Value);
  77. }
  78. }
  79. /// <summary>
  80. /// 访问字段或属性
  81. /// </summary>
  82. /// <param name="expr">表达式</param>
  83. public void VisitMemberExpression(Expression expr)
  84. {
  85. var e = (MemberExpression)expr;
  86. sb.Append(e.Member.Name);
  87. }
  88. }
  89. static void Main(string[] args)
  90. {
  91. //解析复杂表达式
  92. Expression<Func<TestModel, bool>> expr = test => test.Name == "luoma" && test.Id >= 1;
  93. var ex = new ExpressionTypeHelper();
  94. Console.WriteLine(ex.ResolveExpression(expr));
  95. }

输出 ((Name='luoma')And(Id>=1))

例子四-例子三的优化

是不是这么多种的 Expression 类型都得在 Visit 方法添一遍?

ExpressionVisitor 类是提供给我们的表达式树解析的帮助类,我们只要定义一个类继承 ExpressionVisitor,实现一个 ResolveExpression 入口方法,重写
VisitBinary、VisitConstant、VisitMember方法,代码如下

  1. /// <summary>
  2. /// ExpressionType 帮助类
  3. /// </summary>
  4. class ExpressionTypeHelper : ExpressionVisitor
  5. {
  6. StringBuilder sb = new StringBuilder();
  7. /// <summary>
  8. /// 根据相关表达式返回对应格式的字符串
  9. /// </summary>
  10. /// <param name="expr">Expression</param>
  11. /// <returns>对应格式的字符串</returns>
  12. public string ResolveExpression(Expression<Func<TestModel, bool>> expr)
  13. {
  14. Visit(expr.Body);
  15. return sb.ToString();
  16. }
  17. protected override Expression VisitBinary(BinaryExpression node)
  18. {
  19. sb.Append("(");
  20. Visit(node.Left);
  21. sb.Append(node.NodeType.TransferExpressionType());
  22. Visit(node.Right);
  23. sb.Append(")");
  24. return node;
  25. }
  26. protected override Expression VisitConstant(ConstantExpression node)
  27. {
  28. if (node.Type == typeof(string))
  29. {
  30. sb.Append("'" + node.Value + "'");
  31. }
  32. else if (node.Type == typeof(int))
  33. {
  34. sb.Append(node.Value);
  35. }
  36. return node;
  37. }
  38. protected override Expression VisitMember(MemberExpression node)
  39. {
  40. sb.Append(node.Member.Name);
  41. return node;
  42. }
  43. }
  44. static void Main(string[] args)
  45. {
  46. //解析复杂表达式
  47. Expression<Func<TestModel, bool>> expr = test => test.Name == "luoma" && test.Id >= 1;
  48. var ex = new ExpressionTypeHelper();
  49. Console.WriteLine(ex.ResolveExpression(expr));
  50. }

输出 ((Name='luoma')And(Id>=1))

将 Lambda 表达式转换为表达式树

  1. //将 Lambda 表达式转换为类型 Expression<T> 的表达式树
  2. Expression<Func<int, int, int>> expr = (x, y) => x + y;
  3. //获取 Lambda 表达式的主体
  4. BinaryExpression body = (BinaryExpression)expr.Body;
  5. //获取 Lambda 表达式的参数
  6. ParameterExpression left = (ParameterExpression)body.Left;
  7. ParameterExpression right = (ParameterExpression)body.Right;
  8. //创建一个表达式
  9. var w = Expression.Lambda<Func<int, int, int>>(body, new ParameterExpression[] { left, right });
  10. //将表达式树描述的 lambda 表达式编译为可执行代码,并生成表示该 lambda 表达式的委托
  11. Console.WriteLine(w.Compile()(1, 2));

输出 3

表达式树永久性

表达式树应具有永久性(类似字符串)。这意味着如果你想修改某个表达式树,则必须复制该表达式树然后替换其中的节点来创建一个新的表达式树。 你可以使用表达式树访问者遍历现有表达式树。

修改表达式树

加法表达式树访问器

  1. /// <summary>
  2. /// 加法表达式树访问器
  3. /// 该类继承 ExpressionVisitor 类,通过 Visit 方法间接调用 VisitBinary 方法将加法替换成乘法。基类方法构造类似于传入的表达式树的节点,但这些节点将其子目录树替换为访问器递归生成的表达式树
  4. /// </summary>
  5. class AddChangeExpressionVisitor : ExpressionVisitor
  6. {
  7. /// <summary>
  8. /// 访问方法
  9. /// </summary>
  10. /// <param name="node">要访问的表达式</param>
  11. /// <returns>如果修改了该表达式或任何子表达式,则为修改后的表达式;否则返回原始表达式</returns>
  12. public Expression Visit(BinaryExpression node)
  13. {
  14. return VisitBinary(node);
  15. }
  16. /// <summary>
  17. /// 访问 System.Linq.Expressions.BinaryExpression 的子级
  18. /// </summary>
  19. /// <param name="node">要访问的表达式</param>
  20. /// <returns>如果修改了该表达式或任何子表达式,则为修改后的表达式;否则返回原始表达式</returns>
  21. protected override Expression VisitBinary(BinaryExpression node)
  22. {
  23. //如果类型为加法,则变成乘法
  24. return node.NodeType == ExpressionType.Add ? Expression.MakeBinary(ExpressionType.Multiply, node.Left, node.Right) : base.VisitBinary(node);
  25. }
  26. }

Main

  1. //修改表达式树
  2. Expression<Func<int, int, int>> expr = (x, y) => x + y;
  3. var visitor = new AddChangeExpressionVisitor();
  4. var exprChage = (Expression<Func<int, int, int>>)visitor.Visit(expr);
  5. Console.WriteLine("x=1,y=2");
  6. Console.WriteLine("原来的:"+expr+" ,执行结果:"+expr.Compile()(1,2));
  7. Console.WriteLine("改变后的:" + exprChage + " ,执行结果:" + exprChage.Compile()(1,2));

输出

  1. x=1,y=2
  2. 原来的:(x, y) => (x + y) ,执行结果:3
  3. 改变后的:(x, y) => (x * y) ,执行结果:2

调试

参数名称

  1. //定义一个表达式参数名称为 x,类型为 int
  2. ParameterExpression x = Expression.Parameter(typeof(int), "x");
  3. //定义一个表达式参数,类型为 int
  4. ParameterExpression y = Expression.Parameter(typeof(int));

从 DebugView 可知,如果参数没有名称,则会为其分配一个自动生成的名称

参数值类型

  1. ConstantExpression ceX = Expression.Constant(86);
  2. ConstantExpression ceY = Expression.Constant((float)86);

从 DebugView 可知,float 比 int 多了个后缀 F

表达式名称

  1. Expression expr1 = Expression.Lambda<Func<int>>(Expression.Constant(86));
  2. Expression expr2 = Expression.Lambda<Func<int>>(Expression.Constant(86),"CustomName",null);

观察 DebugView ,如果 Lambda 表达式没有名称,则会为其分配一个自动生成的名称

实例-扩展 WhereIn

Model

  1. class TestModel
  2. {
  3. public int Id { get; set; }
  4. public string Name { get; set; }
  5. }

扩展类

  1. /// <summary>
  2. /// QueryableExtend 扩展
  3. /// </summary>
  4. static class QueryableExtend
  5. {
  6. /// <summary>
  7. /// IQueryable<T> 扩展方法
  8. /// </summary>
  9. /// <typeparam name="T">数据类型</typeparam>
  10. /// <typeparam name="TValue">值类型</typeparam>
  11. /// <param name="query">数据列表</param>
  12. /// <param name="valueSelector">表达式</param>
  13. /// <param name="values">值列表</param>
  14. /// <returns>返回对应数据列表</returns>
  15. public static IQueryable<T> WhereIn<T, TValue>(this IQueryable<T> query, Expression<Func<T, TValue>> valueSelector, IEnumerable<TValue> values)
  16. {
  17. return query.Where(BuildContainsExpression(valueSelector, values));
  18. }
  19. /// <summary>
  20. /// 获取符合条件的某个项的表达式
  21. /// </summary>
  22. /// <typeparam name="TElement">数据类型</typeparam>
  23. /// <typeparam name="TValue">值类型</typeparam>
  24. /// <param name="valueSelector">表达式</param>
  25. /// <param name="values">值列表</param>
  26. /// <returns>某个项的表达式</returns>
  27. private static Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values)
  28. {
  29. //获取 Lambda 表达式参数
  30. var p = valueSelector.Parameters.Single();
  31. //判断序列中是否包含元素
  32. if (!values.Any()) return e => false;
  33. //表达式的主体
  34. var equals = values.Select(value => (Expression)Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof(TValue))));
  35. var body = equals.Aggregate(Expression.Or);
  36. //创建一个表达式
  37. return Expression.Lambda<Func<TElement, bool>>(body, p);
  38. }
  39. }

Main

  1. //初始化数据
  2. List<TestModel> models = new List<TestModel>();
  3. models.Add(new TestModel() { Id = 1, Name = "luoma1" });
  4. models.Add(new TestModel() { Id = 2, Name = "luoma2" });
  5. models.Add(new TestModel() { Id = 3, Name = "luoma3" });
  6. var result = models.AsQueryable().WhereIn(t => t.Id, new int[] { 1, 2 });
  7. result.ToList().ForEach(t => { Console.WriteLine(t.Name); });

输出

  1. luoma1
  2. luoma2

附录-表达式类型

表达式类型 描述
BinaryExpression 表示包含二元运算符的表达式
ConditionalExpression 表示包含条件运算符的表达式
ConstantExpression 表示具有常量值的表达式
InvocationExpression 表示将委托或 lambda 表达式应用于参数表达式列表的表达式
LambdaExpression 描述一个 lambda 表达式
MemberExpression 表示访问字段或属性
MethodCallExpression 表示对静态方法或实例方法的调用
NewExpression 表示构造函数调用
NewArrayExpression 表示创建新数组并可能初始化该新数组的元素
MemberInitExpression 表示调用构造函数并初始化新对象的一个或多个成员
ListInitExpression 表示包含集合初始值设定项的构造函数调用
ParameterExpression 表示命名的参数表达式
TypeBinaryExpression 表示表达式和类型之间的操作
UnaryExpression 表示包含一元运算符的表达式