一. 基本介绍

回忆: 最早接触到表达式目录树(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表达式目录树(与委托的区别、自行拼接、总结几类实例间的拷贝)的更多相关文章

  1. Expression表达式目录树

    一.初识Expression 1.在上一篇我们讲到了委托(忘记了可以在看看,点赞在看养成习惯),今天要讲的Expression也和委托有一点点关系吧(没有直接关系,只是想要大家看看我其他的文章),Ex ...

  2. 【学习笔记】Expression表达式目录树

    Expression表达式目录树:一个能拼装能解析的数据结构,语法树. 一.手动拼装表达式目录树 示例1: /// <summary> /// 展示表达式树,协助用的 /// 编译lamb ...

  3. 【手撸一个ORM】第五步、Expression(表达式目录树)转换为Where子句

    说明 在SQL中,查询.修改比较常用到WHERE子句,在这里根据使用场景不同,定义了两个类,一个用于查询,一个用于修改(插入)操作.原因是: 查询操作支持一级导航属性查询,如student.Schoo ...

  4. EXpression 表达式目录树

    表达式树   前面n-1的是一个表达式  最后一个是一个表达式  一直拆开拆到最后 继承ExpressionVisitor的类  可以重写获取到表达式树的方法进行扩张和改写 委托是编译成一个方法 表达 ...

  5. 学习笔记: Expression表达式目录树详解和扩展封装

    1. 表达式链接扩展封装,ORM常用 And  Or /// <summary> /// 表达式访问者 /// </summary> public class Expressi ...

  6. Expression表达式目录树动态拼接 反射获取泛型方法

    class TestOne { public String[] arr = { "1", "2", "3" }; public class ...

  7. Ext JS学习第十六天 事件机制event(一) DotNet进阶系列(持续更新) 第一节:.Net版基于WebSocket的聊天室样例 第十五节:深入理解async和await的作用及各种适用场景和用法 第十五节:深入理解async和await的作用及各种适用场景和用法 前端自动化准备和详细配置(NVM、NPM/CNPM、NodeJs、NRM、WebPack、Gulp/Grunt、G

    code&monkey   Ext JS学习第十六天 事件机制event(一) 此文用来记录学习笔记: 休息了好几天,从今天开始继续保持更新,鞭策自己学习 今天我们来说一说什么是事件,对于事件 ...

  8. 第十九节: 结合【表达式目录树】来封装EF的BaseDal层的方法

    一. 简介 该章节,可以说是一个简单轻松的章节,只要你对Expression表达式树.EF的基本使用.泛型有所了解,那么本章节实质上就是一个非常简单的封装章节,便于我们快捷开发. PS:在该章节对于E ...

  9. 表达式目录树(Expression)

    一:什么是表达式树 Expression我们称为是表达式树,是一种数据结构体,用于存储需要计算,运算的一种结构,这种结构可以只是存储,而不进行运算.通常表达式目录树是配合Lambda一起来使用的,la ...

随机推荐

  1. Windows程序设计:格式化对话框的设计

    刚开始学习Windows程序设计,磕磕碰碰,先做个小笔记缓缓神经,主要是将MessageBox这个Windows API函数的. MessageBox函数是许多人刚开始学习Windows程序设计或者是 ...

  2. ASP.NET -- WebForm -- ScriptManager 类

    ASP.NET -- WebForm -- ScriptManager 类 通过 ScriptManager 可注册随后将作为页面一部分呈现的脚本. 1. 注册并立即执行脚本. --RegisterS ...

  3. SQLServer之CHECK约束

    CHECK约束添加规则 1.CHECK 约束用于限制列中的值的范围. 2.Check约束通过逻辑表达式来判断数据的有效性,用来限制输入一列或多列的值的范围,在列中更新数据时,所要输入的内容必须满足Ch ...

  4. C语言----int (*p)[4] ---思考总结

    a+1  跳4个int (*a)+1 跳一个int

  5. 如何解决代码中if…else 过多的问题

    前言 if...else 是所有高级编程语言都有的必备功能.但现实中的代码往往存在着过多的 if...else.虽然 if...else 是必须的,但滥用 if...else 会对代码的可读性.可维护 ...

  6. socket粘包问题解决

    粘包client.send(data1)client.send(data2)这两次send紧挨在一起,处理的时候会放在一起发过去在Linux里每次都粘包,Windows里面某次会出现粘包在两次send ...

  7. vue 在safari动态多级面包屑导航样式不刷新的bug

    前言: 最近做公司的管理系统,用到了elementUI 里面的 bread面包屑组件,本来一切好好的,谁知道mac的safari样式全部缓存了,硬是下面这种效果,真头疼 而chrome,QQ均显示正常 ...

  8. 修改json对象的每一个值

    function fun1(obj){ var names={}; /*for in 可以用于数组或者对象*/ for(var name in obj){ names[name] = obj[name ...

  9. 3-STM32物联网开发WIFI(ESP8266)+GPRS(Air202)系统方案安全篇(购买域名,域名绑定IP)

    2-STM32物联网开发WIFI(ESP8266)+GPRS(Air202)系统方案安全篇(监听Wi-Fi和APP的数据) 因为安全连接是和域名绑在一块的,所以需要申请域名 有没有不知道域名是什么的, ...

  10. STL中的set使用方法详细!!!!

    1.关于set C++ STL 之所以得到广泛的赞誉,也被很多人使用,不只是提供了像vector, string, list等方便的容器,更重要的是STL封装了许多复杂的数据结构算法和大量常用数据结构 ...