【类型转换】使用c#实现简易的类型转换(Emit,Expression,反射)
引言
哈喽。大家好,好久不见,最近遇到了一个场景,就是在FrameWork的asp.net mvc中,有个系统里面使用的是EntityFramework的框架,在这个框架里,提供了一个SqlQuery的方法,这个方法很好用啊,以至于在EFCORE8里面又添加了回来,不过不知道性能怎么样,我遇到的场景是通过SqlQuery查询的时候,转换很慢,我估计那背后大概率是使用反射造成的, 因为我的查询可能有上十万,甚至更多,就导致了这个转换的过程及其耗时,以至于刚开始我是想通过Emit等方式去实现一个高性能转换,可是到最后没有去弄,因为我用了DataCommand去查询,最后循环DataReader来实现硬赋值,这样性能是最好,一下减少了好多秒,提升了80%,但也给了我一个灵感,一个实现简易的类型转换的灵感,所以在上周我就把代码写了出来,不过由于工作的忙碌,今天才开始写博客,接下来就呈上。
对了,有关EMIT和表达式树的知识,诸位可以看我之前的博客表达式树,IL。其中IL有两合集。
EMIT
众所周知,我们的c#代码在编译器编译,都会编译成IL代码,最后再去通过JIT转化为机器码,运行在系统中去的,所以IL代码的性能是比c#代码高的,同时,学习的成本,编写的成本也是机器高,我也是在自己感兴趣,瞎琢磨,就会了一丝丝皮毛,很多人说IL难写,其实,对于代码中的Opcodes那些,我只记一些常用的,对于剩下的,我都是在写的时候才去看文档,总之呢,要学的东西有很多,但掌握了学习的方法,才是持之以恒的手段。
接下来,就呈上IL代码,分为两部分,一个是List转List,一个是实体转实体的。
在这几个例子中,所有的前提都是实体的属性名称是一样的,如果需要扩展类型不一样,或者哪些不转换,从哪个属性转换到哪个属性,就需要各位自己去扩展了,本来我是想写这些的,,但是懒癌犯了,哈哈哈哈,需要各位看官自己动手了,以下代码,除了反射,其他的我都加了注释,反射大家都看得懂。
在下面的第一个方法,我们定义了执行转换集合的方法,并返回了一个委托,我们在实际开发中,都可以返回委托,最终可以将方法缓存起来,这样在后续的时候直接调用,性能提升爆炸,因为你每次创建Emit方法的时候,耗时也会挺长的,在有时候,像哪些主流的Mapper,他们内部肯定都做了缓存。下面的集合转集合,大致的原理代码就是定义一个方法ConvertToType,返回类型是List<TR>,入参是List<T>,然后定义循环的开始结束变量,以及最终返回结果集,还有循环内部的时候,我们创建的变量,最终将这个变量添加到返回的结果集中,在往下就是拿入参集合的数量,定义循环开始和结束的label,在往下走就是,初始化返回值集合,赋值给本地的localRes变量,将0赋值给开始循环的变量,也就是for(int i=0),下面就是给结束的循环值赋值为入参集合的Count。
下面的代码每行基本都有注释,所以我在这里也不做过多的讲解。
集合和单个的区别就在于集合是多了一个循环的主体,其他都和单个是一样的,以及集合的代码块中,我没有添加try catch的代码块。
internal class EmitExecute<T, TR> : IExecute<T, TR> where T : class where TR : class, new()
{
public Func<List<T>, List<TR>> ExecuteList()
{
var dynamicMethod = new DynamicMethod("ConvertToType", typeof(List<TR>), new Type[] { typeof(List<T>) });
#region 变量定义
var ilMethod = dynamicMethod.GetILGenerator();
var localBegin = ilMethod.DeclareLocal(typeof(int));//定义循环开始变量
var localEnd = ilMethod.DeclareLocal(typeof(int));//结束变量
var localres = ilMethod.DeclareLocal(typeof(List<TR>));//返回值
var localSignleRes = ilMethod.DeclareLocal(typeof(TR));//变量
var countMethod = typeof(List<T>).GetProperty("Count").GetGetMethod();
#endregion
var labelXunhuan = ilMethod.DefineLabel();//定义循环主体
var labelEnd = ilMethod.DefineLabel();//定义结束标签主体
#region 实例化返回值
ilMethod.Emit(OpCodes.Nop);
ilMethod.Emit(OpCodes.Newobj, typeof(List<TR>).GetConstructor(Type.EmptyTypes));//初始化返回值变量
ilMethod.Emit(OpCodes.Stloc, localres);//结果赋值给返回值变量变量
#endregion
#region 给循环的起始变量赋值 0是开始,count是获取的入参的集合的count
ilMethod.Emit(OpCodes.Ldc_I4_0);//将0加载到栈上
ilMethod.Emit(OpCodes.Stloc, localBegin);//给循环的开始值begin赋值0
ilMethod.Emit(OpCodes.Ldarg_0);//加载入参
ilMethod.Emit(OpCodes.Callvirt, countMethod);//将入参的count加载到栈上
ilMethod.Emit(OpCodes.Stloc, localEnd);//给结束变量赋值为集合的count
ilMethod.Emit(OpCodes.Br, labelXunhuan);//无条件跳转到循环的label,即开始循环
#endregion
#region 循环结束标签
ilMethod.MarkLabel(labelEnd);//标记这段代码是labelend的代码 ilMethod.Emit(OpCodes.Ldloc, localres);//加载返回值变量
ilMethod.Emit(OpCodes.Ret);//返回
#endregion
#region 循环主体
ilMethod.MarkLabel(labelXunhuan);//标记是labelbegin的代码
ilMethod.Emit(OpCodes.Ldloc, localBegin);//从栈中加载开始变量
ilMethod.Emit(OpCodes.Ldloc, localEnd);//从栈中加载结束变量
ilMethod.Emit(OpCodes.Bge, labelEnd);//比较第0个是否小于等于第一个 //ilMethod.Emit(OpCodes.Call, typeof(List<People>).GetMethod("Add"));
ilMethod.Emit(OpCodes.Newobj, typeof(TR).GetConstructor(Type.EmptyTypes));//创建people的实例化
ilMethod.Emit(OpCodes.Stloc, localSignleRes);//结果赋值people变量
var properties = typeof(T).GetProperties().Where(s => typeof(TR).GetProperties().Any(a => a.Name == s.Name)).ToList();
if (properties != null)
{
foreach (var item in properties)
{
var getMethod = typeof(T).GetProperty(item.Name).GetGetMethod();//获取list【i】的species属性
var setMethod = typeof(TR).GetProperty(item.Name).GetSetMethod();
ilMethod.Emit(OpCodes.Ldloc, localSignleRes);//加载定义好的people
ilMethod.Emit(OpCodes.Ldarg_0);//加载入参
ilMethod.Emit(OpCodes.Ldloc, localBegin);//加载循环的开始变量
ilMethod.Emit(OpCodes.Callvirt, typeof(List<T>).GetMethod("get_Item"));// 调用 List<Animal>.get_Item 方法,将结果压入栈顶
ilMethod.Emit(OpCodes.Callvirt, getMethod);//拿到getitem的返回值的species属性
ilMethod.Emit(OpCodes.Call, setMethod);//给people的species属性赋值
}
} //ilMethod.Emit(OpCodes.Ldloc, localPeople);//加载定义好的people
//ilMethod.Emit(OpCodes.Ldarg_0);//加载入参
//ilMethod.Emit(OpCodes.Ldloc, localBegin);//加载循环的开始变量
//ilMethod.Emit(OpCodes.Callvirt, typeof(List<T>).GetMethod("get_Item"));// 调用 List<Animal>.get_Item 方法,将结果压入栈顶
//ilMethod.Emit(OpCodes.Callvirt, getAMethod);//拿到getitem的返回值的species属性
//ilMethod.Emit(OpCodes.Call, property);//给people的species属性赋值 ilMethod.Emit(OpCodes.Ldloc, localres);//加载返回值
ilMethod.Emit(OpCodes.Ldloc, localSignleRes);//加载people ilMethod.Emit(OpCodes.Call, typeof(List<TR>).GetMethod("Add"));//将people添加道返回值
ilMethod.Emit(OpCodes.Ldc_I4_1);//将1加载到栈上
ilMethod.Emit(OpCodes.Ldloc, localBegin);//从栈中加载开始变量
ilMethod.Emit(OpCodes.Add);//相加
ilMethod.Emit(OpCodes.Stloc, localBegin);//结果赋值给本地0个局部变量
ilMethod.Emit(OpCodes.Br, labelXunhuan);
#endregion
var func = dynamicMethod.CreateDelegate<Func<List<T>,List<TR>>>();
return func;
} public Func<T, TR> ExecuteSingle()
{
var dynamicMethod = new DynamicMethod("ConvertToType", typeof(TR), new Type[] { typeof(T) });
var method = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) });//输出字符串
var ilMethod = dynamicMethod.GetILGenerator();
var localRes = ilMethod.DeclareLocal(typeof(TR));//变量
var ex = typeof(Exception);
var localEx = ilMethod.DeclareLocal(ex);
var str = typeof(Exception).GetMethod("ToString");
ilMethod.Emit(OpCodes.Nop);
ilMethod.Emit(OpCodes.Newobj, typeof(TR).GetConstructor(Type.EmptyTypes));//初始化返回值变量 new tr();
ilMethod.Emit(OpCodes.Stloc, localRes);//结果赋值给返回值变量变量var tr=new tr();
var properties = typeof(T).GetProperties().Where(s => typeof(TR).GetProperties().Any(a => a.Name == s.Name)).ToList();
if (properties != null)
{
ilMethod.BeginExceptionBlock();//try
foreach (var item in properties)
{
var getMethod = typeof(T).GetProperty(item.Name).GetGetMethod();//获取list【i】的species属性
var setMethod = typeof(TR).GetProperty(item.Name).GetSetMethod();
ilMethod.Emit(OpCodes.Ldloc, localRes);//加载定义好的people
ilMethod.Emit(OpCodes.Ldarg_0);//加载入参
ilMethod.Emit(OpCodes.Callvirt, getMethod);//拿到getitem的返回值的species属性
ilMethod.Emit(OpCodes.Call, setMethod);//给people的species属性赋值
}
ilMethod.BeginCatchBlock(ex);
ilMethod.Emit(OpCodes.Stloc, localEx);// 将异常的ex赋值给我们的本地变量ex
ilMethod.Emit(OpCodes.Ldloc, localEx);//加载ex变量
ilMethod.Emit(OpCodes.Callvirt, str);//调用tostring()
ilMethod.Emit(OpCodes.Call, method);//调用console.writeline方法打印出异常信息
ilMethod.Emit(OpCodes.Newobj, typeof(TR).GetConstructor(Type.EmptyTypes));
ilMethod.Emit(OpCodes.Stloc, localRes);//结果赋值给返回值变量变量
ilMethod.EndExceptionBlock();
}
ilMethod.Emit(OpCodes.Ldloc, localRes);//加载返回值
ilMethod.Emit(OpCodes.Ret);
var func = dynamicMethod.CreateDelegate<Func<T, TR>>();
return func;
}
}
}
Expression
接下来,是表达式树的实现方式,表达式树的其实和Emit的我感觉都差不多,不过和emit相比,肯定大家都喜欢写Expression,毕竟是c#代码,写起来比较舒适,在下面代码就是定义了入参的source,以及从source那指定索引的item,以及返回值res,异常的定义和异常的message,在下面就是循环两个公共属性的信息,调用bind方法,从item的里面拿出sourceproperty的属性和targetproperty绑定,然后给res初始化,设置他的count为source的count,并且判断如果source长度是0,就直接返回一个空的集合,下面有一个构造循环的方法,判断index是否小于集合的count,如果不成立,直接调用break标签,也就是我们的break关键字,如果成立,拿出对应的item,然后调用了MemberInit方法,初始化了一个TR,然后调用Add方法添加到返回的结果集合中,这样就实现了一个一个的转换,最后将所有的表达式整合为一个代码块,通过Block再加入Try Catch,最终编译成一个Func的委托。下面的单个的原理也是一样的。
internal class ExpressionExecute<T, TR> : IExecute<T, TR> where T : class where TR : class, new()
{
private List<PropertyInfo> properties;
public ExpressionExecute()
{
properties = typeof(TR).GetProperties().Where(s => typeof(T).GetProperties().Any(a => a.Name == s.Name)).ToList();//获取相同的属性名称
}
public Func<List<T>, List<TR>> ExecuteList()
{ var sourceParam = Expression.Parameter(typeof(List<T>), "source");//定义入参
var itemVar = Expression.Variable(typeof(T), "item");//定义从集合获取的item变量
var resVar = Expression.Variable(typeof(List<TR>), "res");//定义返回的结果变量
var ex = Expression.Variable(typeof(Exception), "ex");
var msg = Expression.Property(ex, "Message"); var method = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) });
var memberBindings = new List<MemberBinding>();//memberbind
var listExpressions = new List<Expression>();//expression foreach (var property in properties)
{
var sourceProperty = typeof(T).GetProperty(property.Name);
var targetProperty = typeof(TR).GetProperty(property.Name); if (sourceProperty.PropertyType == targetProperty.PropertyType)//判断类型
{
memberBindings.Add(Expression.Bind(targetProperty, Expression.Property(itemVar, sourceProperty)));//相等直接bind add
}
} listExpressions.Add(Expression.Assign(resVar, Expression.New(typeof(List<TR>))));//设置res=new list<tr>
listExpressions.Add(Expression.Assign(Expression.Property(resVar, "Capacity"), Expression.Property(sourceParam, "Count"))); //设置res.count==入参的count var indexVar = Expression.Variable(typeof(int), "index");//循环索引
var breakLabel = Expression.Label("LoopBreak");//break标签 listExpressions.Add(Expression.IfThen(
Expression.Equal(Expression.Property(sourceParam, "Count"), Expression.Constant(0)),//如果source.cout==0
Expression.Return(breakLabel, resVar, typeof(List<TR>)) //直接 break 返回res
));
//构造循环
var innerLoop = CreateLoop(indexVar, sourceParam, itemVar,resVar,memberBindings,breakLabel);
listExpressions.AddRange(new Expression[] { innerLoop , Expression.Label(breakLabel) , resVar });
var body = Expression.Block(new[] { itemVar, indexVar, resVar }, listExpressions);//整合一个代码块
var tryCatch = Expression.TryCatch(body, new CatchBlock[] { Expression.Catch(ex, Expression.Block(Expression.Call(null, method, msg), Expression.New(typeof(List<TR>)))) });//try catch
var lambda = Expression.Lambda<Func<List<T>, List<TR>>>(tryCatch, sourceParam);
return lambda.Compile();
}
private LoopExpression CreateLoop(ParameterExpression indexVar,ParameterExpression sourceParam,ParameterExpression itemVar,ParameterExpression resVar,List<MemberBinding> memberBindings,LabelTarget breakLabel)
{
var addMethod = typeof(List<TR>).GetMethod("Add");//结果集的add方法
return Expression.Loop(
Expression.IfThenElse(//如果index <count 进入block
Expression.LessThan(indexVar, Expression.Property(sourceParam, "Count")),
Expression.Block(
Expression.Assign(itemVar, Expression.Property(sourceParam, "Item", indexVar)),//设置item=source【index】
Expression.Call(resVar, addMethod, Expression.MemberInit(Expression.New(typeof(TR)), memberBindings)),//调用res.add方法
Expression.PostIncrementAssign(indexVar)//index=index+1
),
Expression.Break(breakLabel)//如果index<count不成立,直接中断循环
)
);
}
public Func<T, TR> ExecuteSingle()
{
var express = Expression.Parameter(typeof(T), "source");
var memberBindings = new List<MemberBinding>();//memberbing
var list = new List<Expression>();
var action = new Func<string, string>(s => {
return typeof(TR).GetProperty(s).Name;
});//根据属性名称获取属性,由于property第二个参数必须 string or method,下方就只有他通过call的方式获取属性的name名称
int ipropertydx = 0;//相同属性遍历所以
memberBindings.Clear(); var peoples = Expression.New(typeof(TR));//创建新的tr实例
foreach (var item in properties)//属性遍历
{
var property = Expression.Property(peoples, item.Name);//获取tr实例属性
var memberInfo = typeof(TR).GetMember(item.Name).FirstOrDefault(); // 获取 MemberInfo 对象
if (memberInfo != null)
{
var assignment = Expression.Bind(//将属性和source[index].属性关联起来
memberInfo, Expression.Property( //获取source的属性
express,
//调用上面的委托返回要拿的属性名称也就是A.name=B.name
Expression.Lambda<Func<string>>(
Expression.Call(Expression.Constant(action.Target), action.Method, //调用action拿名称
Expression.Property(
Expression.ArrayIndex(Expression.Constant(properties.ToArray()), Expression.Constant(ipropertydx)), "Name")//获取properties的第ipropertydx的name名称
)
).Compile() //编译为func《string》委托
()
));
memberBindings.Add(assignment);//将每个people的每个属性赋值的assignment添加进去
ipropertydx++;
}
}
var memberInit = Expression.MemberInit(peoples, memberBindings);//将peoples初始化 初始化绑定的每一个成员
var func = Expression.Lambda<Func<T, TR>>(memberInit, express).Compile();//编译为委托
return func;
}
}
反射
反正,反射是很耗时的,少量情况还好,大量并不建议使用,虽然很好用,这里我也只是做一个例子,让我自己用,肯定优选前面两个,这个代码更不用讲了,懂得都懂,
internal class ReflectionExecute<T, TR> : IExecute<T, TR> where T : class where TR : class,new()
{
public Func<List<T>, List<TR>> ExecuteList()
{
var res = new Func<List<T>, List<TR>>(s =>
{
var resList = new List<TR>();
var properties = typeof(T).GetProperties().Where(s => typeof(TR).GetProperties().Any(a => a.Name == s.Name)).ToList();//获取相同的属性名称
foreach (var item in s)
{
var tr = new TR();
foreach (var itemproperty in properties)
{
var val = itemproperty.GetValue(item, null);
if (val != null)
{
var setProperty = typeof(TR).GetProperty(itemproperty.Name);
setProperty.SetValue(tr, val);
}
}
resList.Add(tr);
}
return resList;
});
return res;
} public Func<T, TR> ExecuteSingle()
{
var res = new Func<T, TR>(s =>
{ var properties = typeof(TR).GetProperties().Where(s => typeof(T).GetProperties().Any(a => a.Name == s.Name)).ToList();//获取相同的属性名称
var tr = new TR();
foreach (var itemproperty in properties)
{
var val = itemproperty.GetValue(s, new object[] { });
if (val != null)
{
var setProperty = typeof(TR).GetProperty(itemproperty.Name);
setProperty.SetValue(tr, val);
}
}
return tr;
});
return res;
}
}
测试
这玩意,我自己简单测试了一下,本想用benmark简单跑一下,但是麻烦,就代码自己测试了一下,在第一次构建表达式树的方法,会有些许耗时,但是在最后如果有缓存方法,那性能不必Emit差,总之,性能方面优选Emit和表达式树,反射就不做考虑。
总结
赶鸭子上架,水了一篇博客,如有疑问,可以随时呼我,QQ934550201.
代码地址:
链接:https://pan.baidu.com/s/1vW9LPfYHmvk6Y08qEYA9sw
提取码:vj34
【类型转换】使用c#实现简易的类型转换(Emit,Expression,反射)的更多相关文章
- ExpressionTree,Emit,反射
ExpressionTree,Emit,反射 https://www.cnblogs.com/7tiny/p/9861166.html [前言] 前几日心血来潮想研究着做一个Spring框架,自然地就 ...
- JS 数据类型转换-转换函数、强制类型转换、利用js变量弱类型转换
1. 转换函数: js提供了parseInt()和parseFloat()两个转换函数.前者把值转换成整数,后者把值转换成浮点数.只有对String类型调用这些方法,这两个函数才能正确运行:对其他类型 ...
- 常用类型转换 一.常用int和string类型转换
常用类型转换 一.常用int类型转换1. int.parse(string) 这个类型只支持string类型 2.double doubleType = Int32.MaxValue + 1; i ...
- 类型和原生函数及类型转换(三:终结js类型转换)
Number() parseInt() parseFloat() Boolean() String() toString() 一.显式类型转换 -------Number()函数把对象的值转换为数字. ...
- 再看ExpressionTree,Emit,反射创建对象性能对比
[前言] 前几日心血来潮想研究着做一个Spring框架,自然地就涉及到了Ioc容器对象创建的问题,研究怎么高性能地创建一个对象.第一联想到了Emit,兴致冲冲写了个Emit创建对象的工厂.在做性能测试 ...
- Emit优化反射(属性的设置与获取)
在频繁的通过反射来设置和获取属性的值时是比较耗时的,本章通过Emit技术优化反射来提高获取和设置属性值的效率 一.实现代码: /// <summary> /// 设置器委托 /// < ...
- Android简易注解View(java反射实现)
一.引言 Android中通过findViewById在布局文件中找到需要的View,加入一个Activity里面有许多的View需要初始化,那将是一件很繁琐的事情.当然Google一下你会发现有很多 ...
- 使用 IL 实现类型转换
在之前的文章中,我大致介绍过一些类型间的隐式和显式类型转换规则.但当时并未很仔细的研究过<CSharp Language Specification>,因此实现并不完整.而且只部分解决了类 ...
- struts2类型转换
1. Struts2中的类型转换 我们知道通过HTTP提交到后台的数据,都是字符串的形式,而我们需要的数据类型当然不只字符串类型一种.所以,我们需要类型转换! 在Struts2中,类型转换的概念除了用 ...
- C++_系列自学课程_第_11_课_类型转换_《C++ Primer 第四版》
上次说了关于表达式的一些内容,说到还有一些关于数据类型转换的内容,今天我们接着八一八C++中的数据类型转换. 一.隐式类型转换 在表达式中,有些操作符可以对多种类型的操作数进行操作, 例如 + 操作符 ...
随机推荐
- defined('BASEPATH') OR exit('No direct script access allowed'); 的作用
起到保护.php文件的作用, 如果直接访问此php文件会得到"不允许直接访问脚本"的错误提示 如果你是用ci框架或者其他的什么, 就建议加上, 如果你怕别人恶意攻击你的话
- Go类型全解:常量与变量大全!
本篇文章深入探讨了 Go 语言中类型确定值.类型不确定值以及对应类型转换的知识点,后续充分解析了常量与变量及其高级用法,并举出丰富的案例. 关注公众号[TechLeadCloud],分享互联网架构.云 ...
- 升讯威在线客服系统的并发高性能数据处理技术:PLINQ并行查询技术
我在业余时间开发维护了一款免费开源的升讯威在线客服系统,也收获了许多用户.对我来说,只要能获得用户的认可,就是我最大的动力. 最近客服系统成功经受住了客户现场组织的压力测试,获得了客户的认可. 客户组 ...
- 【.NET8】访问私有成员新姿势UnsafeAccessor(上)
前言 前几天在.NET性能优化群里面,有群友聊到了.NET8新增的一个特性,这个类叫UnsafeAccessor,有很多群友都不知道这个特性是干嘛的,所以我就想写一篇文章来带大家了解一下这个特性. 其 ...
- Vue2系列(lqz)——6-Vue-cli、7-Vue插件、8-Vue第三方框架之ElementUi
文章目录 6 Vue-CLI 项目搭建 1 单文件组件 2 Vue-CLI 项目搭建 2.1 环境搭建 2.2 项目的创建 创建项目 启动/停止项目 打包项目 package.json中 2.3 认识 ...
- replace批量替换、表删除数据查询用法
一. select * from baec_file where bacti='1'order by baec01; select baec02,REPLACE(baec02,'白班','A班') f ...
- scnhealthcheck
在CPU补丁中,Oracle提供了一个脚本 scnhealthcheck.sql 用于检查数据库当前SCN的剩余情况.该脚本的算法和以上描述相同,最终将最大合理SCN 减去当前数据库SCN,计算得出一 ...
- Pushpin:开源即时通信神器,让你的API秒变实时API,轻松实现WebSocket,HTTP流和HTTP长轮询等服务
作为一个开发者,你可能已经利用过REST API来构建和集成各种应用.REST API是基于HTTP协议的交互模式,它使得客户端和服务器可以通过请求和响应来进行数据交换,简单.灵活.通用. 然而,当你 ...
- 差异行压缩算法(C#实现)
private byte[] DifferenceRowOrder(int offset, int count, byte[] inbyte)//差异行命令(此处的offset和count都从1开始) ...
- 搓一个Pythonic list
总所周知,Python语言当中的list是可以存储不同类型的元素的,对应到现代C++当中,可以用std::variant或者std::any实现类似的功能.而Python官方的实现当中用到了二级指 ...