第十五节:Expression表达式目录树(与委托的区别、自行拼接、总结几类实例间的拷贝)
一. 基本介绍
回忆: 最早接触到表达式目录树(Expression)可能要追溯到几年前使用EF早期的时候,发现where方法里的参数是Expression<Func<T,bool>>这么一个类型,当初不是很理解,只是知道传入lambda表达式使用即可,对于Expression和里面的Func<T,bool>到底是怎么一种关系,都不清楚。
今天,带着回忆开发初期的心情,详细的介绍一下这一段时间对Expression的理解。
1. Expression与Func委托的区别
①:委托是一种类型,是方法的抽象,通过委托可以将方法以参数的形式传递给另一个方法,同时调用委托的时候,它缩包含的方法都会被实现。委托的关键字是delegate,可以自定义委托,也可以使用内置委托,通过简化,可以将Lambda表达式或Lambda语句赋值给委托,委托的调用包括同步调用和异步调用。
②:表达式目录树(Expression),是一种数据结构,可以利用Lambda表达式进行声明,Lambda表达式的规则要符合Expression中Func委托的参数规则。
可以利用Lambda表达式进行声明,但Lambda语句是不能声明的。
Expression调用Compile方法可以转换成<TDelegate>中的委托。
回顾委托的代码:
{
//1. Func委托,必须要有返回值,最后一个参数为返回值,前面为输入参数
Func<int, int, int> func1 = new Func<int, int, int>((int m, int n) =>
{
return m * n + ;
});
//对其进行最简化(Lambda语句)
Func<int, int, int> func2 = (m, n) =>
{
return m * n + ;
};
//对其进行最简化(Lambda表达式)
Func<int, int, int> func3 = (m, n) => m * n + ;
//调用委托
int result1 = func1.Invoke(, );
int result2 = func2.Invoke(, );
int result3 = func3.Invoke(, );
Console.WriteLine("委托三种形式结果分别为:{0},{1},{2}", result1, result2, result3);
}
初识Expression表达式目录树的代码
{
//报错 (Lambda语句无法转换成表达式目录树)
//Expression<Func<int, int, int>> exp1 = (m, n) =>
//{
// return m * n + 2;
//};
//利用Lambda表达式 来声明 表达式目录树
Expression<Func<int, int, int>> exp2 = (m, n) => m * n + ;
//利用Compile编译,可以将表达式目录树转换成委托
Func<int, int, int> func = exp2.Compile();
int result1 = func.Invoke(, );
Console.WriteLine("表达式目录树转换成委托后结果为:{0}", result1);
}
执行结果

2. 自己拼接表达式目录树
①. 核心:先把Lambda表达式写出来,然后用ILSpy工具进行编译,就能把表达式目录树的拼接过程给显示出来,当然有些代码不是我们想要的,需要转换成C#代码。
②. 了解拼接要用到的类型和方法:
类型:常量值表达式(ConstantExpression)、命名参数表达式(ParameterExpression)、含二元运算符的表达式(BinaryExpression)
方法:设置常量表达式的值(Constant方法)、设置参数表达式的变量(Parameter方法)、相加(Add方法)、相乘(Multiply方法)、Lambda方法:将拼接的二元表达式转换成表达式目录树
③. 步骤:声明变量和常量→进行二元计算→将最终二元运算符的表达式转换成表达式目录树
下面分享拼接 Expression<Func<int, int, int>> express1 = (m, n) => m * n + 2; 的代码:
{
Func<int, int, int> func1 = (m, n) => m * n + ;
//利用反编译工具翻译下面这个Expression
Expression<Func<int, int, int>> express1 = (m, n) => m * n + ;
//下面是反编译以后的代码(自己稍加改进了一下)
ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "m");
ParameterExpression parameterExpression2 = Expression.Parameter(typeof(int), "n");
BinaryExpression binaryExpression = Expression.Multiply(parameterExpression, parameterExpression2);
ConstantExpression constantExpression = Expression.Constant(, typeof(int));
BinaryExpression binaryFinalBody = Expression.Add(binaryExpression, constantExpression);
Expression<Func<int, int, int>> express = Expression.Lambda<Func<int, int, int>>(binaryFinalBody, new ParameterExpression[]
{
parameterExpression,
parameterExpression2
});
int result = express.Compile().Invoke(, );
Console.WriteLine("自己拼接的表达式目录树的结果为:{0}", result);
}
运行结果:

二. 实体间Copy赋值的几类处理方案
背景: 在实际开发中,我们可能经常会遇到这种场景,两个实体的名称不同,属性完全相同,需要将一个实体的值赋值给另一个对应实体上的属性。
解决这类问题通常有以下几种方案:
1. 直接硬编码的形式:速度最快(0.126s)
2. 通过反射遍历属性的形式 (6.328s)
3. 利用序列化和反序列化的形式:将复制实体序列化字符串,在把该字符串反序列化被赋值实体(7.768s)
4. 字典缓存+表达式目录树(Lambda的拼接代码了解即可) (0.663s)
5. 泛型缓存+表达式目录树(Lambda的拼接代码了解即可) (2.134s)
代码分享:
public static class CopyUtils
{
//字典缓存
private static Dictionary<string, object> _Dic = new Dictionary<string, object>(); public static void Show()
{
//0. 准备实体
User user = new User()
{
id = ,
userName = "ypf",
userAge =
};
long time1 = ;
long time2 = ;
long time3 = ;
long time4 = ;
long time5 = ; #region 1-直接硬编码的形式
{
Task.Run(() =>
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = ; i < ; i++)
{
UserCopy userCopy = new UserCopy()
{
id = user.id,
userName = user.userName,
userAge = user.userAge,
};
}
watch.Stop();
time1 = watch.ElapsedMilliseconds;
Console.WriteLine("方案1所需要的时间为:{0}", time1);
}); }
#endregion #region 2-反射遍历属性
{
Task.Run(() =>
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = ; i < ; i++)
{
CopyUtils.ReflectionMapper<User, UserCopy>(user);
}
watch.Stop();
time2 = watch.ElapsedMilliseconds;
Console.WriteLine("方案2所需要的时间为:{0}", time2);
});
}
#endregion #region 3-序列化和反序列化
{
Task.Run(() =>
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = ; i < ; i++)
{
CopyUtils.SerialzerMapper<User, UserCopy>(user);
}
watch.Stop();
time3 = watch.ElapsedMilliseconds;
Console.WriteLine("方案3所需要的时间为:{0}", time3);
}); }
#endregion #region 04-字典缓存+表达式目录树
{
Task.Run(() =>
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = ; i < ; i++)
{
CopyUtils.DicExpressionMapper<User, UserCopy>(user);
}
watch.Stop();
time4 = watch.ElapsedMilliseconds;
Console.WriteLine("方案4所需要的时间为:{0}", time4);
});
}
#endregion #region 05-泛型缓存+表达式目录树
{
Task.Run(() =>
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = ; i < ; i++)
{
GenericExpressionMapper<User, UserCopy>.Trans(user);
}
watch.Stop();
time5 = watch.ElapsedMilliseconds;
Console.WriteLine("方案5所需要的时间为:{0}", time5);
});
}
#endregion }
上述代码涉及到的几个封装
#region 封装-反射的方式进行实体间的赋值
/// <summary>
/// 反射的方式进行实体间的赋值
/// </summary>
/// <typeparam name="TIn">赋值的实体类型</typeparam>
/// <typeparam name="TOut">被赋值的实体类型</typeparam>
/// <param name="tIn"></param>
public static TOut ReflectionMapper<TIn, TOut>(TIn tIn)
{
TOut tOut = Activator.CreateInstance<TOut>();
//外层遍历获取【被赋值的实体类型】的属性
foreach (var itemOut in tOut.GetType().GetProperties())
{
//内层遍历获取【赋值的实体类型】的属性
foreach (var itemIn in tIn.GetType().GetProperties())
{
if (itemOut.Name.Equals(itemIn.Name))
{
//特别注意这里:SetValue和GetValue的用法
itemOut.SetValue(tOut, itemIn.GetValue(tIn));
break;
}
}
}
return tOut;
}
#endregion #region 封装-序列化反序列化进行实体见的赋值
/// <summary>
/// 序列化反序列化进行实体见的赋值
/// </summary>
/// <typeparam name="TIn">赋值的实体类型</typeparam>
/// <typeparam name="TOut">被赋值的实体类型</typeparam>
/// <param name="tIn"></param>
public static TOut SerialzerMapper<TIn, TOut>(TIn tIn)
{
return JsonConvert.DeserializeObject<TOut>(JsonConvert.SerializeObject(tIn));
}
#endregion #region 封装-字典缓存+表达式目录树
/// <summary>
/// 序列化反序列化进行实体见的赋值
/// </summary>
/// <typeparam name="TIn">赋值的实体类型</typeparam>
/// <typeparam name="TOut">被赋值的实体类型</typeparam>
/// <param name="tIn"></param>
public static TOut DicExpressionMapper<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);
}
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);
}
#endregion
/// <summary>
/// 泛型缓存
/// </summary>
/// <typeparam name="TIn"></typeparam>
/// <typeparam name="TOut"></typeparam>
public class GenericExpressionMapper<TIn, TOut>
{
private static Func<TIn, TOut> _FUNC = null;
static GenericExpressionMapper()
{
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);
}
泛型缓存
最终运行结果:

三. 剥离表达式目录树
这里补充几个常用的表达式目录树的拼接代码:And、Or、Not 。
ExpressionExtend扩展类代码:
public static class ExpressionExtend
{
/// <summary>
/// 合并表达式 expr1 AND expr2
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expr1"></param>
/// <param name="expr2"></param>
/// <returns></returns>
public static Expression<Func<T, bool>> And<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.And(left, right);
return Expression.Lambda<Func<T, bool>>(body, newParameter); }
/// <summary>
/// 合并表达式 expr1 or expr2
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expr1"></param>
/// <param name="expr2"></param>
/// <returns></returns>
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);
}
public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expr)
{
var candidateExpr = expr.Parameters[];
var body = Expression.Not(expr.Body); return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
}
}
NewExpressionVisitor建立新表达式辅助类代码:
/// <summary>
/// 建立新表达式
/// </summary>
internal class NewExpressionVisitor : ExpressionVisitor
{
public ParameterExpression _NewParameter { get; private set; }
public NewExpressionVisitor(ParameterExpression param)
{
this._NewParameter = param;
}
public Expression Replace(Expression exp)
{
return this.Visit(exp);
}
protected override Expression VisitParameter(ParameterExpression node)
{
return this._NewParameter;
}
}
如何使用的代码:
public class VisitorUtils
{
public static void Show()
{
Expression<Func<User, bool>> lambda1 = x => x.userAge > ;
Expression<Func<User, bool>> lambda2 = x => x.id > ;
Expression<Func<User, bool>> lambda3 = lambda1.And(lambda2);
Expression<Func<User, bool>> lambda4 = lambda1.Or(lambda2);
Expression<Func<User, bool>> lambda5 = lambda1.Not(); Do1(lambda1);
Do1(lambda2);
Do1(lambda3);
Do1(lambda4);
Do1(lambda5);
} private static void Do1(Expression<Func<User, bool>> func)
{
List<User> user = new List<User>()
{
new User(){id=,userName="",userAge=},
new User(){id=,userName="",userAge=},
new User(){id=,userName="",userAge=},
}; List<User> peopleList = user.Where(func.Compile()).ToList();
}
}

第十五节:Expression表达式目录树(与委托的区别、自行拼接、总结几类实例间的拷贝)的更多相关文章
- Expression表达式目录树
一.初识Expression 1.在上一篇我们讲到了委托(忘记了可以在看看,点赞在看养成习惯),今天要讲的Expression也和委托有一点点关系吧(没有直接关系,只是想要大家看看我其他的文章),Ex ...
- 【学习笔记】Expression表达式目录树
Expression表达式目录树:一个能拼装能解析的数据结构,语法树. 一.手动拼装表达式目录树 示例1: /// <summary> /// 展示表达式树,协助用的 /// 编译lamb ...
- 【手撸一个ORM】第五步、Expression(表达式目录树)转换为Where子句
说明 在SQL中,查询.修改比较常用到WHERE子句,在这里根据使用场景不同,定义了两个类,一个用于查询,一个用于修改(插入)操作.原因是: 查询操作支持一级导航属性查询,如student.Schoo ...
- EXpression 表达式目录树
表达式树 前面n-1的是一个表达式 最后一个是一个表达式 一直拆开拆到最后 继承ExpressionVisitor的类 可以重写获取到表达式树的方法进行扩张和改写 委托是编译成一个方法 表达 ...
- 学习笔记: Expression表达式目录树详解和扩展封装
1. 表达式链接扩展封装,ORM常用 And Or /// <summary> /// 表达式访问者 /// </summary> public class Expressi ...
- Expression表达式目录树动态拼接 反射获取泛型方法
class TestOne { public String[] arr = { "1", "2", "3" }; public class ...
- Ext JS学习第十六天 事件机制event(一) DotNet进阶系列(持续更新) 第一节:.Net版基于WebSocket的聊天室样例 第十五节:深入理解async和await的作用及各种适用场景和用法 第十五节:深入理解async和await的作用及各种适用场景和用法 前端自动化准备和详细配置(NVM、NPM/CNPM、NodeJs、NRM、WebPack、Gulp/Grunt、G
code&monkey Ext JS学习第十六天 事件机制event(一) 此文用来记录学习笔记: 休息了好几天,从今天开始继续保持更新,鞭策自己学习 今天我们来说一说什么是事件,对于事件 ...
- 第十九节: 结合【表达式目录树】来封装EF的BaseDal层的方法
一. 简介 该章节,可以说是一个简单轻松的章节,只要你对Expression表达式树.EF的基本使用.泛型有所了解,那么本章节实质上就是一个非常简单的封装章节,便于我们快捷开发. PS:在该章节对于E ...
- 表达式目录树(Expression)
一:什么是表达式树 Expression我们称为是表达式树,是一种数据结构体,用于存储需要计算,运算的一种结构,这种结构可以只是存储,而不进行运算.通常表达式目录树是配合Lambda一起来使用的,la ...
随机推荐
- spring mybatics
spring boot web.mysql.mybatics.jps 遇到jar包版本不行的直接换个版本Add进去 https://github.com/forezp/SpringBootLe ...
- springboot中,页面访问不到静态资源
例一,静态资源放在默认的目录,如:resources/static或resources/templates 访问静态资源的时候,路径不应带上默认目录,因为springboot默认从这些目录下开始加载, ...
- (九)Delete an Index
Now let’s delete the index that we just created and then list all the indexes again: 现在让我们删除刚刚创建的索引, ...
- Redis数据持久化、数据备份、数据的故障恢复
1.redis持久化的意义----redis故障恢复 在实际的生产环境中,很可能会遇到redis突然挂掉的情况,比如redis的进程死掉了.电缆被施工队挖了(支付宝例子)等等,总之一定会遇到各种奇葩的 ...
- 2019-03-23 shell练习,日志统计
[2019-03-10 17:44:55]1234567 paid 10 10000 cat test.txt |tail -n +1 |sed 's/]/ ]g' |awk '{count[$3]+ ...
- AI Conditional GAN
Conditional GAN 参考链接: https://arxiv.org/pdf/1611.07004v1.pdf
- Sklearn中的回归和分类算法
一.sklearn中自带的回归算法 1. 算法 来自:https://my.oschina.net/kilosnow/blog/1619605 另外,skilearn中自带保存模型的方法,可以把训练完 ...
- C#7.0中的解构功能---Deconstruct
解构元组 C#7.0新增了诸多功能,其中有一项是新元组(ValueTuple),它允许我们可以返回多个值,并且配合解构能更加方便的进行工作,如下面例子 static void Main(string[ ...
- sqlServer:行列转换之多行转一行
1.建表:学生表(姓名,学科,成绩) CREATE TABLE teststudent( stuname varchar(50) NULL, subjects varchar(50) NU ...
- maven eclipse web 项目 问题 cannot change version of project facet dynamic web module to 3.0
cannot change version of project facet dynamic web module to 3.0 修改 web.xml 头部 xsi:schemaLocation=&q ...