C# 表达式目录树 Expression
什么是表达式目录树
这个是委托
Func<int, int, int> func = (m, n) => m * n + 2;
Console.WriteLine("func:" + func(2, 3));//调用
//输出:func:8
这个是表达式树
Expression<Func<int, int, int>> expression = (m, n) => m * n + 2;
var result = expression.Compile();//返回的是个委托方法
Console.WriteLine("expression:" + result(2, 3));//调用
//输出:expression:8
表达式树和委托十分相似,但是它们是有区别的。委托是不能被修改的,而表达式树是可以被修改的。
表达式树就像一个数据结构体,用来存储计算逻辑的一种结构。
动态拼装表达式目录树
我们使用反编译工具反编译这段代码,来剖析它
Expression<Func<int, int, int>> expression = (m, n) => m * n + 2;
下面是反编译后的代码。看完之后,我们发现表达式树被通过Lambda声明之后编译为
//声明一个参数名称为 m 的参数表达式 parameterExpression
ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "m");
//声明一个参数名称为 n 的参数表达式 parameterExpression2
ParameterExpression parameterExpression2 = Expression.Parameter(typeof(int), "n");
//得到 参数表达式 parameterExpression, parameterExpression2 的乘法运算的二进制表达式 multiplyBinaryExpression
BinaryExpression multiplyBinaryExpression = Expression.Multiply(parameterExpression, parameterExpression2);
//声明一个值为 2 的常量表达式
ConstantExpression constantExpression = Expression.Constant(2, typeof(int));
//得到 二进制表达式 multiplyBinaryExpression 与常量表达式 constantExpression 的加法运算的二进制表达式 addBinaryExpression
BinaryExpression addBinaryExpression = Expression.Add(multiplyBinaryExpression, constantExpression);
//参数表达式数组
ParameterExpression[] parameters = new ParameterExpression[]
{
parameterExpression,
parameterExpression2
};
//声明表达式树
Expression<Func<int, int, int>> expression = Expression.Lambda<Func<int, int, int>>(addBinaryExpression, parameters);
我们也可以尝试拼装自己的表达式树
拼装自己的表达式树
(x,y)=>x+y-2;
//声明两个参数表达式
ParameterExpression parameterExpressionX = Expression.Parameter(typeof(int), "x");
ParameterExpression parameterExpressionY = Expression.Parameter(typeof(int), "y");
//声明一个常量为2的常量表达式
ConstantExpression constantExpression = Expression.Constant(2, typeof(int));
//将x与y相乘 得到新的表达式树
BinaryExpression addExpression = Expression.Add(parameterExpressionX, parameterExpressionY);
//将新的表达式 与 常量表达式详见
BinaryExpression subExpression = Expression.Subtract(addExpression, constantExpression);
//声明一个Lambda 表达式
Expression<Func<int, int, int>> expression = Expression.Lambda<Func<int, int, int>>(subExpression, new ParameterExpression[] { parameterExpressionX, parameterExpressionY });
Console.WriteLine("myExpression:" + expression.Compile()(4, 5));//调用
//输出:myExpression:7
当然也可以拼装更为复杂的 如:
Expression<Func<Student, bool>> expression1 = (s) => s.ID.Equals(5);
拼装代码
ConstantExpression constantExpression = Expression.Constant(5, typeof(Int32));
ParameterExpression parameterExpression = Expression.Parameter(typeof(Student), "s");
MemberExpression memberExpression = Expression.Property(parameterExpression, typeof(Student).GetProperty("ID").GetMethod);
MethodInfo methodInfo = typeof(Int32).GetMethod("Equals", new Type[] { typeof(Int32) });
MethodCallExpression methodCallExpression = Expression.Call(memberExpression, methodInfo, new Expression[] { constantExpression });
Expression<Func<Student, bool>> expression2 = Expression.Lambda<Func<Student, bool>>(methodCallExpression, new ParameterExpression[] { parameterExpression });
Console.WriteLine("expression2:" + expression2.ToString());//输出:expression2:s => s.ID.Equals(5)
扩展应用
当然有的朋友就会问了,这种有什么用呢?我来给大家举个例子(虽然很无聊)
比如有这样一个需求,需要将Student对象转换为StudentCopy对象
创建类
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public int Age;
}
public class StudentCopy
{
public int ID { get; set; }
public string Name { get; set; }
public int Age;
}
对象初始化
Student student = new Student() { ID = 1, Age = 18, Name = "Oliver" };
方法一,直接硬编码:
当然这个方法有个缺点,不灵活。
StudentCopy studentCopy = new StudentCopy() { ID = student.ID, Age = student.Age, Name = student.Name };
方法二,使用反射:
代码
/// <summary>
/// 通过反射完成对象映射
/// </summary>
public class ReflectionMapping
{
public static outT Trans<inT, outT>(inT obj)
{
var newObj = (outT)Activator.CreateInstance(typeof(outT));
foreach (PropertyInfo propertyInfo in typeof(inT).GetProperties())
{
typeof(outT).GetProperty(propertyInfo.Name).SetValue(newObj, propertyInfo.GetValue(obj));
}
foreach (FieldInfo fieldInfo in typeof(inT).GetFields())
{
typeof(outT).GetField(fieldInfo.Name).SetValue(newObj, fieldInfo.GetValue(obj));
}
return newObj;
}
}
调用
StudentCopy studentCopy = ReflectionMapping.Trans<Student, StudentCopy>(student);
方法三,使用表达式树
当然还可以使用我们今天说的主题--表达式树
我们先写一个Lambda形式的表达式树,再将其反编译,最后将里面的字段和属性通过动态的方式获取出来。
Expression<Func<Student, StudentCopy>> expression1 = (s) => new StudentCopy() { ID = s.ID, Age = s.Age, Name = s.Name };
ParameterExpression parameterExpression = Expression.Parameter(typeof(Student), "s");
MethodInfo methodInfo = typeof(StudentCopy).GetProperty("ID").GetMethod;
MemberAssignment idMemberAssignment = Expression.Bind(typeof(StudentCopy).GetProperty("ID").SetMethod, Expression.Property(parameterExpression, typeof(Student).GetProperty("ID").GetMethod));
MemberAssignment ageMemberAssignment = Expression.Bind(typeof(StudentCopy).GetField("Age"), Expression.Field(parameterExpression, typeof(Student).GetField("Age")));
MemberAssignment nameMemberAssignment = Expression.Bind(typeof(StudentCopy).GetProperty("Name").SetMethod, Expression.Property(parameterExpression, typeof(Student).GetProperty("Name").GetMethod));
Expression<Func<Student, StudentCopy>> expression = Expression.Lambda<Func<Student, StudentCopy>>(Expression.MemberInit(Expression.New(typeof(StudentCopy)), new MemberBinding[]
{
idMemberAssignment,
ageMemberAssignment,
nameMemberAssignment
}), new ParameterExpression[]
{
parameterExpression
});
StudentCopy studentCopy = expression.Compile()(student);
方法四,通过字典封装下
我们将上面的代码封装下,将表达式树中的func使用字典缓存起来
代码:
public class ExpressionDictMapping<TIn, TOut>
{
private static Dictionary<string, Func<TIn, TOut>> _Dict = new Dictionary<string, Func<TIn, TOut>>();
public static TOut Trans(TIn obj)
{
string key = string.Format("key_{0}_{1}", typeof(TIn).Name, typeof(TOut).Name);
if (!_Dict.ContainsKey(key))
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "s");
MethodInfo methodInfo = typeof(TOut).GetProperty("ID").GetMethod;
List<MemberAssignment> list = new List<MemberAssignment>();
foreach (PropertyInfo propertyInfo in typeof(TIn).GetProperties())
{
MemberAssignment memberAssignment = Expression.Bind(typeof(TOut).GetProperty(propertyInfo.Name).SetMethod, Expression.Property(parameterExpression, propertyInfo.GetMethod));
list.Add(memberAssignment);
}
foreach (FieldInfo fieldInfo in typeof(TIn).GetFields())
{
MemberAssignment memberAssignment = Expression.Bind(typeof(TOut).GetField(fieldInfo.Name), Expression.Field(parameterExpression, fieldInfo));
list.Add(memberAssignment);
}
MemberBinding[] memberBindings = list.ToArray<MemberBinding>();
Expression<Func<TIn, TOut>> expression = Expression.Lambda<Func<TIn, TOut>>(
Expression.MemberInit(Expression.New(typeof(TOut)), memberBindings)
, new ParameterExpression[] { parameterExpression }
);
_Dict[key] = expression.Compile();
}
return _Dict[key](obj);
}
}
调用:
StudentCopy studentCopy = ExpressionDictMapping<Student, StudentCopy>.Trans(student);
StudentCopy studentCopy2 = ExpressionDictMapping<Student, StudentCopy>.Trans(student);
方法五,通过泛型封装(之前的泛型文章有提到泛型缓存数据)
我们使用泛型的方式缓存起来,这样更加的高效。
代码:
public class ExpressionGenericMapping<TIn, TOut>
{
private static Func<TIn, TOut> func = null;
static ExpressionGenericMapping()
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "s");
MethodInfo methodInfo = typeof(TOut).GetProperty("ID").GetMethod;
List<MemberAssignment> list = new List<MemberAssignment>();
foreach (PropertyInfo propertyInfo in typeof(TIn).GetProperties())
{
MemberAssignment memberAssignment = Expression.Bind(typeof(TOut).GetProperty(propertyInfo.Name).SetMethod, Expression.Property(parameterExpression, propertyInfo.GetMethod));
list.Add(memberAssignment);
}
foreach (FieldInfo fieldInfo in typeof(TIn).GetFields())
{
MemberAssignment memberAssignment = Expression.Bind(typeof(TOut).GetField(fieldInfo.Name), Expression.Field(parameterExpression, fieldInfo));
list.Add(memberAssignment);
}
MemberBinding[] memberBindings = list.ToArray<MemberBinding>();
Expression<Func<TIn, TOut>> expression = Expression.Lambda<Func<TIn, TOut>>(
Expression.MemberInit(Expression.New(typeof(TOut)), memberBindings)
, new ParameterExpression[] { parameterExpression }
);
func = expression.Compile();
}
public static TOut Trans(TIn obj)
{
return func(obj);
}
}
调用
StudentCopy studentCopy = ExpressionGenericMapping<Student, StudentCopy>.Trans(student);
StudentCopy studentCopy2 = ExpressionGenericMapping<Student, StudentCopy>.Trans(student);
比较效率
然后我们比较下上面各个版本的效率
//普通方式
long common = 0;
//反射方式
long reflection = 0;
//dict缓存方式
long dict = 0;
//Generic 缓存方式
long generic = 0;
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1000000; i++)
{
StudentCopy studentCopy = new StudentCopy()
{
ID = student.ID,
Name = student.Name,
Age = student.Age
};
}
watch.Stop();
common = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1000000; i++)
{
StudentCopy studentCopy = ReflectionMapping.Trans<Student, StudentCopy>(student);
}
watch.Stop();
reflection = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1000000; i++)
{
StudentCopy studentCopy = ExpressionDictMapping<Student, StudentCopy>.Trans(student);
}
watch.Stop();
dict = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1000000; i++)
{
StudentCopy studentCopy = ExpressionGenericMapping<Student, StudentCopy>.Trans(student);
}
watch.Stop();
generic = watch.ElapsedMilliseconds;
}
Console.WriteLine($"common = { common} ms");
Console.WriteLine($"reflection = { reflection} ms");
Console.WriteLine($"dict = { dict} ms");
Console.WriteLine($"generic = { generic} ms");
/*
输出:
common = 22 ms
reflection = 1780 ms
dict = 339 ms
generic = 94 ms
*/
通过执行100w次 输出的时间来看,毫无疑问普通的硬编码方式执行时间是最快的(但是不灵活),然后是通过泛型缓存表达式树,再是通过字典缓存表达式树,最后是反射最慢。
修改表达式树
最上面我们就提到表达式树是可以被修改的而委托的方法是不能被修改的,现在我们就试着修改下下面这个表达式树
Expression<Func<int, int, int>> expression = (m, n) => m * n + 2;
我们希望将这个表达式修改为: (m, n) => m * n - 2;(将+变为-)。
目的就是为了让我们更加了解表达式树
修改表达式树必须使用这个类 ExpressionVisitor ,但是因为这个类是抽象类所以我们必须实例化它
//
// 摘要:
// 表示表达式树的访问者或重写者。
public abstract class ExpressionVisitor
实例化:
public class MyExpressionVisitor : ExpressionVisitor
{
public Expression Modify(Expression expression)
{
return this.Visit(expression);
}
protected override Expression VisitBinary(BinaryExpression node)
{
//判断操作是否是+ ,如果是则修改为 -
if (node.NodeType == ExpressionType.Add)
{
Expression left = node.Left;
Expression right = node.Right;
return Expression.Subtract(left, right);
}
return base.VisitBinary(node);
}
}
调用:
Expression<Func<int, int, int>> expression = (m, n) => m * n + 2;
//我们希望将这个表达式修改为: (m, n) => m * n - 2;(将+变为-)//目的就是为了让我们更加了解表达式树
MyExpressionVisitor myExpressionVisitor = new MyExpressionVisitor();
Expression<Func<int, int, int>> newExpression = (Expression<Func<int, int, int>>)(myExpressionVisitor.Modify(expression));
Console.WriteLine("expression:" + expression.Compile()(2, 3));//输出8
Console.WriteLine("newExpression:" + newExpression.Compile()(2, 3));//输出4
表达式树拼装组合条件
在工作中我经常会遇到以下情况:
根据前台传入的条件不同,筛选不同的数据,这种情况下如果单纯的使用IF判断非常痛苦。但是如果使用我们的表达式树就非常轻松了。
if判断语句
List<Student> list = null;
bool isAge = true;
bool isID = true;
if (isAge && !isID)
{
list = students.Where(c => c.Age == 10).ToList();
}
else if (isID && !isAge)
{
list = students.Where(c => c.ID > 5).ToList();
}
else if (isID && isAge)
{
list = students.Where(c => c.ID > 5 && c.Age == 10).ToList();
}
else if (!isID && !isAge)
{
list = students.ToList();
}
表达式树拼装组合条件
实例化ExpressionVisitor类。(因为需要动态的修改表达式树的参数,所以需要覆写)
public class NewExpressionVisitor : ExpressionVisitor
{
/// <summary>
/// 将参数表达式 存储起来
/// </summary>
private ParameterExpression _ParameterExpression = null;
public NewExpressionVisitor(ParameterExpression expression)
{
_ParameterExpression = expression;
}
public Expression Replace(Expression expression)
{
return base.Visit(expression);
}
/// <summary>
/// 访问ParameterExpression 时会进入该方法,在该方法里面将参数替换
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
protected override Expression VisitParameter(ParameterExpression node)
{
return this._ParameterExpression;
}
}
将转换代码封装到扩展类中
/// <summary>
/// 定义一个表达式树的扩展类
/// </summary>
public static class ExpressionExtend
{
/// <summary>
/// and 方法
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expression1"></param>
/// <param name="expression2"></param>
/// <returns></returns>
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
{
return expression1.Trans(expression2, Expression.And);
}
/// <summary>
/// or 方法
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expression1"></param>
/// <param name="expression2"></param>
/// <returns></returns>
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
{
return expression1.Trans(expression2, Expression.Or);
}
/// <summary>
/// 转换方法
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expression1">表达式树1</param>
/// <param name="expression2">表达式树2</param>
/// <param name="func">需要执行的方法</param>
/// <returns></returns>
private static Expression<Func<T, bool>> Trans<T>(this Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2, Func<Expression, Expression, BinaryExpression> func)
{
if (expression1 == null) return expression2;
if (expression2 == null) return expression1;
//重新声明一个参数表达式
ParameterExpression parameterExpression = Expression.Parameter(typeof(T), "c");
//将参数表达式传递给我们新定义的NewExpressionVisitor
NewExpressionVisitor newExpressionVisitor = new NewExpressionVisitor(parameterExpression);
//得到表达式的左边
var left = newExpressionVisitor.Replace(expression1.Body);
//得到表达式的右边
var right = newExpressionVisitor.Replace(expression2.Body);
//使用相应的方法拼接
var body = func(left, right);
//返回新的表达式树
return Expression.Lambda<Func<T, bool>>(body, parameterExpression);
}
}
调用
调用时就非常的灵活了 ,可以根据传入的条件灵活的进行操作。
List<Student> students = new List<Student>();
students.Add(new Student() { ID = 1, Age = 10, Name = "A" });
students.Add(new Student() { ID = 2, Age = 20, Name = "A2" });
students.Add(new Student() { ID = 3, Age = 10, Name = "A3" });
students.Add(new Student() { ID = 4, Age = 20, Name = "A4" });
students.Add(new Student() { ID = 5, Age = 10, Name = "A5" });
students.Add(new Student() { ID = 6, Age = 20, Name = "A6" });
students.Add(new Student() { ID = 7, Age = 10, Name = "A7" });
students.Add(new Student() { ID = 8, Age = 20, Name = "A8" });
students.Add(new Student() { ID = 9, Age = 10, Name = "A9" });
students.Add(new Student() { ID = 10, Age = 20, Name = "A10" });
students.Add(new Student() { ID = 11, Age = 10, Name = "A11" });
students.Add(new Student() { ID = 12, Age = 20, Name = "A12" });
students.Add(new Student() { ID = 13, Age = 10, Name = "A13" });
students.Add(new Student() { ID = 14, Age = 20, Name = "A14" });
students.Add(new Student() { ID = 15, Age = 10, Name = "A15" });
students.Add(new Student() { ID = 16, Age = 20, Name = "A16" });
Expression<Func<Student, bool>> express1 = (s) => s.ID > 5;
Expression<Func<Student, bool>> express2 = (s) => s.Age == 10;
Expression<Func<Student, bool>> express3 = express1.And(express2);
var list1 = students.Where(express1.Compile());
var list2 = students.Where(express2.Compile());
var list3 = students.Where(express3.Compile());
Console.WriteLine("
list1 表达式:{0} 结果:", express1.ToString());
Console.WriteLine(string.Join("
", list1.Select(c => $"ID:{ c.ID} Name:{c.Name} Age:{c.Age}")));
Console.WriteLine("
list2 表达式:{0} 结果:", express2.ToString());
Console.WriteLine(string.Join("
", list2.Select(c => $"ID:{ c.ID} Name:{c.Name} Age:{c.Age}")));
Console.WriteLine("
list3 表达式:{0} 结果:", express3.ToString());
Console.WriteLine(string.Join("
", list3.Select(c => $"ID:{ c.ID} Name:{c.Name} Age:{c.Age}")));
/*
输出:
list1 表达式:s => (s.ID > 5) 结果:
ID:6 Name:A6 Age:20
ID:7 Name:A7 Age:10
ID:8 Name:A8 Age:20
ID:9 Name:A9 Age:10
ID:10 Name:A10 Age:20
ID:11 Name:A11 Age:10
ID:12 Name:A12 Age:20
ID:13 Name:A13 Age:10
ID:14 Name:A14 Age:20
ID:15 Name:A15 Age:10
ID:16 Name:A16 Age:20
list2 表达式:s => (s.Age == 10) 结果:
ID:1 Name:A Age:10
ID:3 Name:A3 Age:10
ID:5 Name:A5 Age:10
ID:7 Name:A7 Age:10
ID:9 Name:A9 Age:10
ID:11 Name:A11 Age:10
ID:13 Name:A13 Age:10
ID:15 Name:A15 Age:10
list3 表达式:c => ((c.ID > 5) And (c.Age == 10)) 结果:
ID:7 Name:A7 Age:10
ID:9 Name:A9 Age:10
ID:11 Name:A11 Age:10
ID:13 Name:A13 Age:10
ID:15 Name:A15 Age:10
*/