简言:
相信各位在写程序时都会碰到,对象拷贝的问题。例如:
class PersonEntity { public long Id { get; set; } public string Name { get; set; } public string XXX { get; set; } public int Age { get; set; } public DateTime CreateTime { get; set; } = DateTime.Now; } class PersonDTO { public long Id { get; set; } public string Name { get; set; } public string XXX { get; set; } public int Age { get; set; } }
将一个PersonEntity对象的属性值,赋值给一个PersonDTO对象。一般我们的处理方式就是写一个TOXXX方法。但是如果下次换一个类,那就又得写一个TOXXX方法。而且如果类添加或者减少一个字段,那么就得修改这个方法,如果字段少,倒还好,字段一多,就凉凉。到了这里,我想大家一定会想到用反射解决这个问题,但反射有个效率问题。今天我介绍一种新的方法:那就是表达式树。说到表达式树,如果你用过EF,一定知道xxxx.Where(e=>e.Id==1)这个语法,where 方法里的语句很是一个lambda表达式,其实它是一个Expression<Func<T,bool>>的表达式树。下面我们来看看:
例如:
static void Main(string[] args) { Expression<Func<PersonEntity, PersonDTO>> ex = e => new PersonDTO { Id = e.Id, Name = e.Name }; PersonDTO dto = ex.Compile()(new PersonEntity { Id = 1, Name = "xxx" }); Console.ReadKey(); }
我们来看下编译器将它编译成什么样吧:
1 private static void Main(string[] args) 2 { 3 ParameterExpression parameterExpression = Expression.Parameter(typeof(PersonEntity), "e"); 4 Expression<Func<PersonEntity, PersonDTO>> expression = Expression.Lambda<Func<PersonEntity, PersonDTO>>(Expression.MemberInit(Expression.New(typeof(PersonDTO)), new MemberBinding[] 5 { 6 Expression.Bind((MethodInfo)MethodBase.GetMethodFromHandle(ldtoken(set_Id())), Expression.Property(parameterExpression, (MethodInfo)MethodBase.GetMethodFromHandle(ldtoken(get_Id())))), 7 Expression.Bind((MethodInfo)MethodBase.GetMethodFromHandle(ldtoken(set_Name())), Expression.Property(parameterExpression, (MethodInfo)MethodBase.GetMethodFromHandle(ldtoken(get_Name())))) 8 }), new ParameterExpression[] 9 { 10 parameterExpression 11 }); 12 PersonDTO personDTO = expression.Compile()(new PersonEntity 13 { 14 Id = 1L, 15 Name = "xxx" 16 }); 17 Console.ReadKey(); 18 }
同志们,不要慌,我们来给他梳理下,然后就变成我们看的懂的了:
1 Type dtoType = typeof(PersonDTO); 2 Type entityType = typeof(PersonEntity); 3 4 //创建一个参数表达式 5 ParameterExpression parameterExpression = Expression.Parameter(entityType, "e"); 6 7 //创建一个访问属性表达式 8 MemberExpression idMemberEx = Expression.Property(parameterExpression, entityType.GetProperty("Id")); 9 //创建一个属性或者字段初始化表达式 10 MemberBinding idMemberBinding= Expression.Bind(dtoType.GetProperty("Id"),idMemberEx); 11 12 //创建一个访问属性表达式 13 MemberExpression nameMemberEx = Expression.Property(parameterExpression, entityType.GetProperty("Name")); 14 //创建一个属性或者字段初始化表达式 15 MemberBinding nameMemberBinding = Expression.Bind(dtoType.GetProperty("Name"), nameMemberEx); 16 17 var properMemberBindings = new MemberBinding[] { 18 idMemberBinding,nameMemberBinding 19 }; 20 //创建一个创建对象表达式,它会自动调用该类型的无参构造函数 21 NewExpression dtonewex = Expression.New(dtoType); 22 ///创建表达式主体。用于填充 System.Linq.Expressions.MemberBinding 集合的 System.Linq.Expressions.MemberInitExpression.Bindings 对象的数组。 23 MemberInitExpression memberInit = Expression.MemberInit(dtonewex, properMemberBindings); 24 25 //创建一个lambda表达式,表达式主体是memberInit,参数是parameterExpression 26 Expression<Func<PersonEntity, PersonDTO>> expression = Expression.Lambda<Func<PersonEntity, PersonDTO>>(memberInit, new ParameterExpression[] 27 { 28 parameterExpression 29 }); 30 //将表达式树生成一个委托 31 PersonDTO personDTO = expression.Compile()(new PersonEntity 32 { 33 Id = 1L, 34 Name = "xxx" 35 }); 36 Console.WriteLine(personDTO.Name);
运行结果为:
我们可以将它封装下,变成我们想要的万能的TOXXX方法
public class ValTransformationCommon<TEntity, TRes> where TEntity : class, new() where TRes : class, new() { private static IDictionary<string, Func<TEntity, TRes>> mapperDicts = new ConcurrentDictionary<string, Func<TEntity, TRes>>(); /// <summary> /// 实体映射,暂且不能映射导航属性。规则是:类中的属性名字必须一样 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="obj"></param> /// <returns></returns> public static TRes Mapper(TEntity obj) { Type enType = typeof(TEntity); Type resType = typeof(TRes); string key = $"mapperKey_{enType.Name}_{resType.Name}"; //判断缓存里是否存在该委托 if (mapperDicts.ContainsKey(key)) { return mapperDicts[key](obj); } //创建表达式树参数 ParameterExpression parameter = Expression.Parameter(enType, "e"); List<MemberBinding> list = new List<MemberBinding>(); //遍历属性 foreach (var item in resType.GetProperties()) { var pro = enType.GetProperty(item.Name); if (pro == null) { continue; } MemberExpression propertyExpression = Expression.Property(parameter, pro); MemberBinding bind = Expression.Bind(item, propertyExpression); list.Add(bind); } //遍历字段 foreach (var item in resType.GetFields()) { var filed = enType.GetField(item.Name); if (filed == null) { continue; } MemberExpression property = Expression.Field(parameter, filed); MemberBinding bind = Expression.Bind(item, property); list.Add(bind); } //将属性值和字段值复制给新的对象, MemberInitExpression init = Expression.MemberInit(Expression.New(resType), list); //获取表达式树 Expression<Func<TEntity, TRes>> expression = Expression.Lambda<Func<TEntity, TRes>>(init, parameter); Func<TEntity, TRes> func = expression.Compile(); //将委托缓存起来 mapperDicts[key] = func; return func(obj); } }
那么它和反射相比,那个效率高呢?下面我们来对比下吧:
反射的方法:
static TResult TOXXX<TResult, TEntity>(TEntity entity) where TEntity : class, new() where TResult : class, new() { Type entityType = typeof(TEntity); Type resType = typeof(TResult); TResult result = (TResult)Activator.CreateInstance(resType); foreach (var item in entityType.GetProperties()) { var pro = resType.GetProperty(item.Name); if (resType.GetProperty(item.Name) == null) { continue; } pro.SetValue(result,item.GetValue(entity)); } foreach (var item in entityType.GetFields()) { var pro = resType.GetField(item.Name); if (resType.GetProperty(item.Name) == null) { continue; } pro.SetValue(result, item.GetValue(entity)); } return result; }
测试的代码:
{ //for (int i = 0; i < 1000; i++) //{ // Stopwatch stop = new Stopwatch(); // stop.Start(); // var en = new PersonEntity { Id = 1, Name = "1111", XXX="xxx", Age=1 }; // var dto = new PersonDTO { Age = en.Age, Id = en.Id, Name = en.Name, XXX = en.XXX }; // stop.Stop(); // Console.WriteLine(stop.Elapsed.TotalMilliseconds); //} } { //for (int i = 0; i < 1000; i++) //{ // Stopwatch stop = new Stopwatch(); // stop.Start(); // var dto = TOXXX<PersonDTO, PersonEntity>(new PersonEntity { Id = 1, Name = "1111", XXX = "xxx", Age = 1 }); // stop.Stop(); // Console.WriteLine(stop.Elapsed.TotalMilliseconds); //} } { for (int i = 0; i < 1000; i++) { Stopwatch stop = new Stopwatch(); stop.Start(); var dto = ValTransformationCommon<PersonEntity, PersonDTO>.Mapper(new PersonEntity { Id = 1, Name = "1111", XXX = "xxx", Age = 1 }); stop.Stop(); Console.WriteLine(stop.Elapsed.TotalMilliseconds); } }
我们来试下10万次的赋值看下各自的消耗时间吧
硬编码:
反射:
表达式树:
表达式树除了第一次执行很慢,后面基本等同于硬编码的速度了。效率比反射快5-6倍。