Expression表达式目录树:一个能拼装能解析的数据结构,语法树。

一、手动拼装表达式目录树

示例1:

/// <summary>
/// 展示表达式树,协助用的
/// 编译lambda--反编译C#--得到原始声明方式
/// </summary>
public class ExpressionTreeVisualizer
{
public static void Show()
{
//lambda表达式声明表达式目录树(快捷方式),是一个数据结构
//不能有语句体,只能是一行,不能有大括号
Expression<Func<int, int, int>> expression = (m, n) => m * n + m + n + 2;
//int iResult = expression.Compile().Invoke(23, 34);
}
}

上面的这种方式是使用lambda表达式快捷的声明表达式目录树,那我们怎么手动拼装这样子的一个表达式目录树呢?

此处我们借助反编译工具ILSpy,编译lambda--反编译C#--得到原始声明方式

反编译示例1后得到的结果如下:

public static void Show()
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "m");
ParameterExpression parameterExpression2 = Expression.Parameter(typeof(int), "n");
Expression<Func<int, int, int>> expression =
Expression.Lambda<Func<int, int, int>>(Expression.Add(Expression.Add(
Expression.Add(Expression.Multiply(parameterExpression, parameterExpression2), parameterExpression), parameterExpression2),
Expression.Constant(2, typeof(int))),
new ParameterExpression[]
{
parameterExpression,
parameterExpression2
});
}

然后根据反编译后得到的结果作为参考,再手动拼装表达式目录树,如下所示:

{
//Expression<Func<int, int, int>> expression = (m, n) => m * n + m + n + 2;
//int iResult = expression.Compile().Invoke(23, 34); ParameterExpression m = Expression.Parameter(typeof(int), "m");
ParameterExpression n = Expression.Parameter(typeof(int), "n");
var constant = Expression.Constant(2); var mutiply = Expression.Multiply(m, n);
var plus1 = Expression.Add(mutiply, m);
var plus2 = Expression.Add(plus1, n);
var plus3 = Expression.Add(plus2, constant);
Expression<Func<int, int, int>> expression = Expression.Lambda<Func<int, int, int>>(plus3, new ParameterExpression[] { m, n });
int iResult = expression.Compile().Invoke(23, 34);
}

运行后就可以发现我们动态的拼装出来了:

示例2:

/// <summary>
/// 展示表达式树,协助用的
/// 编译lambda--反编译C#--得到原始声明方式
/// </summary>
public class ExpressionTreeVisualizer
{
public static void Show()
{
//lambda表达式声明表达式目录树(快捷方式),是一个数据结构
//不能有语句体,只能是一行,不能有大括号
//Expression<Func<int, int, int>> expression = (m, n) => m * n + m + n + 2;
//int iResult = expression.Compile().Invoke(23, 34); Expression<Func<People, bool>> lambda = x => x.Id.ToString().Equals("5");
}
}

反编译示例2后得到的结果如下:

public static void Show()
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(People), "x");
Expression<Func<People, bool>> expression =
Expression.Lambda<Func<People, bool>>(Expression.Call(Expression.Call(Expression.Field(parameterExpression,
FieldInfo.GetFieldFromHandle(ldtoken(Id))), (MethodInfo)MethodBase.GetMethodFromHandle(ldtoken(ToString())), new Expression[0]),
(MethodInfo)MethodBase.GetMethodFromHandle(ldtoken(Equals())), new Expression[]
{
Expression.Constant("5", typeof(string))
}), new ParameterExpression[]
{
parameterExpression
});
}

同理我们根据反编译后得到的结果作为参考,再手动拼装表达式目录树,如下所示:

{
//Expression<Func<People, bool>> lambda = x => x.Id.ToString().Equals("5"); ParameterExpression parameterExpression = Expression.Parameter(typeof(People), "x");
var constantExp = Expression.Constant("5");
FieldInfo field = typeof(People).GetField("Id");
var fieldExp = Expression.Field(parameterExpression, field);
var toString = typeof(int).GetMethod("ToString", new Type[] { });
var toStringExp = Expression.Call(fieldExp, toString, new Expression[0]);
var equals = typeof(string).GetMethod("Equals", new Type[] { typeof(string) });
var equalsExp = Expression.Call(toStringExp, equals, new Expression[] { constantExp });
Expression<Func<People, bool>> expression = Expression.Lambda<Func<People, bool>>(equalsExp, new ParameterExpression[]
{
parameterExpression
}); bool bResult = expression.Compile()(new People()
{
Id = 5,
Name = "浪子天涯",
Age = 20
});
}

运行结果如下:

手动拼装表达式目录树的目的:

  1、使用lambda表达式声明表达式目录树(快捷方式)这是硬编码,硬编码就无法做到动态,但是我们可以借助手动拼装表达式目录树来动态生成硬编码。

  2、表达式目录树可以转换成委托,手动拼装表达式目录树就相当于可以动态生成委托。

  3、表达式目录树是一个能拼装能解析的数据结构,语法树。手动拼装成功后,后面就可以根据实际需要去解析表达式目录树完成相应的操作。

二、表达式目录树应用场景

下面我们来看个简单的例子:

需求:一个Web应用通过前端收集用户的输入成为Dto,然后将Dto转换成领域模型并持久化到数据库中。也就是说在实际的软件开发项目中,我们的“业务逻辑”常常需要我们对同样的数据进行各种变换。

解决这个问题我们的常规做法是:使用AutoMapper来完成Dto与Model之间的实体映射。

此处引发一个思考:如果让我们自己来完成实体映射,那么我们有哪些解决思路呢?

准备测试用的实体和Dto:

/// <summary>
/// 实体
/// </summary>
public class People
{
public int Age { get; set; } public string Name { get; set; } public int Id;
} /// <summary>
/// Dto
/// </summary>
public class DtoPeople
{
public int Age { get; set; } public string Name { get; set; } public int Id;
}

第1种解决思路:硬编码

People people = new People()
{
Id = 1,
Name = "测试",
Age = 22
}; for (int i = 0; i < 1_000_000; i++)
{
DtoPeople dtoPeople = new DtoPeople()
{
Id = people.Id,
Name = people.Name,
Age = people.Age
};
}

第2种解决思路:反射

/// <summary>
/// 反射映射
/// </summary>
public class ReflectionMapper
{
/// <summary>
/// 实体转换
/// </summary>
/// <typeparam name="T">传入类型</typeparam>
/// <typeparam name="TResult">返回值类型</typeparam>
/// <param name="tIn">传入参数</param>
/// <returns>转换好的实体</returns>
public static TResult Trans<T, TResult>(T tIn)
{
TResult tOut = Activator.CreateInstance<TResult>();
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;
}
}

第3种解决思路:序列化反序列化

/// <summary>
/// 使用第三方序列化反序列化工具
/// </summary>
public class SerializeMapper
{
/// <summary>
/// 实体转换
/// </summary>
public static TResult Trans<T, TResult>(T tIn)
{
return JsonConvert.DeserializeObject<TResult>(JsonConvert.SerializeObject(tIn));
}
}

第4种解决思路:表达式目录树 + 字典缓存

/// <summary>
/// 生成表达式目录树 字典缓存
/// </summary>
public class ExpressionMapper
{
/// <summary>
/// 字典缓存--hash分布
/// </summary>
private static Dictionary<string, object> _dic = new Dictionary<string, object>(); /// <summary>
/// 实体转换
/// </summary>
public static TResult Trans<T, TResult>(T tIn)
{
string key = string.Format("funckey_{0}_{1}", typeof(T).FullName, typeof(TResult).FullName);
if (!_dic.ContainsKey(key))
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(T), "p");
List<MemberBinding> memberBindingList = new List<MemberBinding>();
foreach (var item in typeof(TResult).GetProperties())
{
MemberExpression property = Expression.Property(parameterExpression, typeof(T).GetProperty(item.Name));
MemberBinding memberBinding = Expression.Bind(item, property);
memberBindingList.Add(memberBinding);
}
foreach (var item in typeof(TResult).GetFields())
{
MemberExpression property = Expression.Field(parameterExpression, typeof(T).GetField(item.Name));
MemberBinding memberBinding = Expression.Bind(item, property);
memberBindingList.Add(memberBinding);
}
MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TResult)), memberBindingList.ToArray());
Expression<Func<T, TResult>> lambda = Expression.Lambda<Func<T, TResult>>(memberInitExpression, new ParameterExpression[]
{
parameterExpression
});
Func<T, TResult> func = lambda.Compile(); //调用Compile方法将表达式转换成委托
_dic[key] = func; //拼装是一次性的
} return ((Func<T, TResult>)_dic[key]).Invoke(tIn);
}
}

第5种解决思路:表达式目录树 + 泛型缓存(泛型缓存特点:为不同类型的组合去缓存一个结果。)

/// <summary>
/// 生成表达式目录树 泛型缓存
/// </summary>
/// <typeparam name="T">传入参数类型</typeparam>
/// <typeparam name="TResult">返回值类型</typeparam>
public class ExpressionGenericMapper<T, TResult>
{
/// <summary>
/// 泛型缓存
/// </summary>
private static Func<T, TResult> _func = null; /// <summary>
/// 静态构造函数(只会被调用一次)
/// </summary>
static ExpressionGenericMapper()
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(T), "p");
List<MemberBinding> memberBindingList = new List<MemberBinding>();
foreach (var item in typeof(TResult).GetProperties())
{
MemberExpression property = Expression.Property(parameterExpression, typeof(T).GetProperty(item.Name));
MemberBinding memberBinding = Expression.Bind(item, property);
memberBindingList.Add(memberBinding);
}
foreach (var item in typeof(TResult).GetFields())
{
MemberExpression property = Expression.Field(parameterExpression, typeof(T).GetField(item.Name));
MemberBinding memberBinding = Expression.Bind(item, property);
memberBindingList.Add(memberBinding);
}
MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TResult)), memberBindingList.ToArray());
Expression<Func<T, TResult>> lambda = Expression.Lambda<Func<T, TResult>>(memberInitExpression, new ParameterExpression[]
{
parameterExpression
});
_func = lambda.Compile();//拼装是一次性的
} /// <summary>
/// 实体转换
/// </summary>
public static TResult Trans(T t)
{
return _func(t);
}
}

下面我们来测试下这5种方案的性能:

/// <summary>
/// 性能测试
/// </summary>
public static void MapperTest()
{
People people = new People()
{
Id = 1,
Name = "测试",
Age = 22
}; 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 < 1_000_000; i++)
{
DtoPeople dtoPeople = new DtoPeople()
{
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 < 1_000_000; i++)
{
DtoPeople dtoPeople = ReflectionMapper.Trans<People, DtoPeople>(people);
}
watch.Stop();
reflection = watch.ElapsedMilliseconds;
}
//序列化反序列化
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1_000_000; i++)
{
DtoPeople dtoPeople = SerializeMapper.Trans<People, DtoPeople>(people);
}
watch.Stop();
serialize = watch.ElapsedMilliseconds;
}
//表达式目录树 + 字典缓存
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1_000_000; i++)
{
DtoPeople dtoPeople = ExpressionMapper.Trans<People, DtoPeople>(people);
}
watch.Stop();
cache = watch.ElapsedMilliseconds;
}
//表达式目录树 + 泛型缓存
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1_000_000; i++)
{
DtoPeople dtoPeople = ExpressionGenericMapper<People, DtoPeople>.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"); //性能比AutoMapper还要高
}

来看下运行结果:

从运行结果可以看出硬编码的性能是最高的,其次就是我们的表达式目录树 + 泛型缓存,这2个的性能几乎是同一个数量级的。

小结:

  1、既需要考虑动态(通用),又要保证性能(硬编码)---动态生成硬编码---表达式目录树拼装(动态生成委托)(得到的就是硬编码)。

  2、如果使用反射来实现某个功能时性能不高,可以考虑使用表达式目录树拼装来实现这个功能(动态生成委托)。

  3、不能将具有语句体的Lambda表达式转换为表达式目录树。(即只能是一行且不能有大括号)

反编译工具ILSpy:

链接:https://pan.baidu.com/s/1ngh2AK9HrKLVLi8ng8vcrQ
提取码:c9x5

【学习笔记】Expression表达式目录树的更多相关文章

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

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

  2. 第十五节:Expression表达式目录树(与委托的区别、自行拼接、总结几类实例间的拷贝)

    一. 基本介绍 回忆: 最早接触到表达式目录树(Expression)可能要追溯到几年前使用EF早期的时候,发现where方法里的参数是Expression<Func<T,bool> ...

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

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

  4. Expression表达式目录树

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

  5. EXpression 表达式目录树

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

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

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

  7. C#表达式目录树(Expression)

    1.什么是表达式目录树 :简单的说是一种语法树,或者说是一种数据结构(Expression) 2.用Lambda声明表达式目录树: Expression<Func<; //表达试目录树的方 ...

  8. C#简单实现表达式目录树(Expression)

    1.什么是表达式目录树 :简单的说是一种语法树,或者说是一种数据结构(Expression) 2.用Lambda声明表达式目录树: 1 2 3 4 5 Expression<Func<in ...

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

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

随机推荐

  1. 05 jumpserver权限管理

    5.权限管理: 在同一个组下的用1户之间,资产是共享的. (1)为10.0.0.121_slavenode1资产授权: (2)为10.0.0.201_lc-pc资产授权: (3)查看授权列表:

  2. 分布式唯一ID生成方案选型!详细解析雪花算法Snowflake

    分布式唯一ID 使用RocketMQ时,需要使用到分布式唯一ID 消息可能会发生重复,所以要在消费端做幂等性,为了达到业务的幂等性,生产者必须要有一个唯一ID, 需要满足以下条件: 同一业务场景要全局 ...

  3. webpack(4)webpack.config.js配置和package.json配置

    前言 上一篇文章我们使用webpack打包成功了,但是每次都要自己手动输入打包的文件地址和打包到哪里去的地址,非常麻烦,所以这里介绍使用配置文件进行打包 webpack.config.js 首先我们创 ...

  4. CentOS-常用命令(版本:7.x)

    常用命令 注:centos命令不定期持续更新,希望能够帮到你~ 修改主机名 $ hostnamectl set-hostname xxx 查看IP $ vim /etc/sysconfig/netwo ...

  5. CentOS-yum安装Redis(单点)

    源文件安装(推荐安装) 在CentOS系统中,首先安装EPEL仓库,然后更新yum源: $ yum install epel-release -y $ yum update -y 然后安装Redis数 ...

  6. php-高级计算器

    HTML代码: <!doctype html><html lang="en"><head> <meta charset="UTF ...

  7. HGAME_easyVM

    64位的exe,拖入ida64静态分析一波. 一.先是一大堆的赋值语句,有点懵,后面在分析handler的时候,也直接导致了我卡壳,这里还是得注意一下这些局部变量都是临近的,所以可以直接看成一个连续数 ...

  8. 不同版本docker修改存储位置补充

    前言:最近发现yum安装docker,安装的版本不一样,有点蛇皮,虽然存放默认位置都是/var/lib/docker,但是它的配置文件不一样,这里做个补充 对于docker版本是1.13及以下 操作如 ...

  9. tcp三次握手四次挥手----转

    序列号seq:占4个字节,用来标记数据段的顺序,TCP把连接中发送的所有数据字节都编上一个序号,第一个字节的编号由本地随机产生:给字节编上序号后,就给每一个报文段指派一个序号:序列号seq就是这个报文 ...

  10. (学习心路历程)Vue过渡/动画 VS. 过渡/动画

    [此篇为本人的个人见解和哔哔赖赖,如果有观点不对的地方,还请大家指出来哇!!] 最近实习在做一个项目,里面应用的动画效果还蛮复杂的,因为本身对Vue框架比较熟悉,所以最终选择了Vue框架. 自己之前从 ...