服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - C# 快速高效率复制对象(表达式树)

C# 快速高效率复制对象(表达式树)

2021-12-30 14:51Emrys5 C#

在代码中经常会遇到需要把对象复制一遍,或者把属性名相同的值复制一遍。本文将对其解决方法进行详细介绍。下面跟着小编一起来看下吧

1、需求

在代码中经常会遇到需要把对象复制一遍,或者把属性名相同的值复制一遍。

比如:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Student
 {
  public int Id { get; set; }
  public string Name { get; set; }
  public int Age { get; set; }
 }
 
 public class StudentSecond
 {
  public int Id { get; set; }
  public string Name { get; set; }
  public int Age { get; set; }
 }

Student s = new Student() { Age = 20, Id = 1, Name = "Emrys" };

我们需要给新的Student赋值

Student ss = new Student { Age = s.Age, Id = s.Id, Name = s.Name };

再或者给另一个类StudentSecond的属性赋值,两个类属性的名称和类型一致。

StudentSecond ss = new StudentSecond { Age = s.Age, Id = s.Id, Name = s.Name };

2、解决办法

当然最原始的办法就是把需要赋值的属性全部手动手写。这样的效率是最高的。但是这样代码的重复率太高,而且代码看起来也不美观,更重要的是浪费时间,如果一个类有几十个属性,那一个一个属性赋值岂不是浪费精力,像这样重复的劳动工作更应该是需要优化的。

2.1、反射

反射应该是很多人用过的方法,就是封装一个类,反射获取属性和设置属性的值。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
private static TOut TransReflection<TIn, TOut>(TIn tIn)
  {
   TOut tOut = Activator.CreateInstance<TOut>();
   foreach (var itemOut in tOut.GetType().GetProperties())
   {
    var itemIn = tIn.GetType().GetProperties().Where(i => i.Name == itemOut.Name).FirstOrDefault();
    if (itemIn != null)
    {
     itemOut.SetValue(tOut, itemIn.GetValue(tIn));
    }
   }
   return tOut;
  }

调用:StudentSecond ss= TransReflection<Student, StudentSecond>(s);

调用一百万次耗时:2464毫秒

2.2、序列化

序列化的方式有很多种,有二进制、xml、json等等,今天我们就用Newtonsoft的json进行测试。

调用:

?
1
StudentSecond ss= JsonConvert.DeserializeObject<StudentSecond>(JsonConvert.SerializeObject(s));

调用一百万次耗时:2984毫秒

从这可以看出序列化和反射效率差别不大。

3、表达式树

3.1、简介

关于表达式树不了解的可以百度。

也就是说复制对象也可以用表达式树的方式。

?
1
2
3
Expression<Func<Student, StudentSecond>> ss = (x) => new StudentSecond { Age = x.Age, Id = x.Id, Name = x.Name };
var f = ss.Compile();
StudentSecond studentSecond = f(s);

这样的方式我们可以达到同样的效果。

有人说这样的写法和最原始的复制没有什么区别,代码反而变多了呢,这个只是第一步。

3.2、分析代码

我们用ILSpy反编译下这段表达式代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
ParameterExpression parameterExpression;
 Expression<Func<Student, StudentSecond>> ss = Expression.Lambda<Func<Student, StudentSecond>>(Expression.MemberInit(Expression.New(typeof(StudentSecond)), new MemberBinding[]
 {
  Expression.Bind(methodof(StudentSecond.set_Age(int)), Expression.Property(parameterExpression, methodof(Student.get_Age()))),
  Expression.Bind(methodof(StudentSecond.set_Id(int)), Expression.Property(parameterExpression, methodof(Student.get_Id()))),
  Expression.Bind(methodof(StudentSecond.set_Name(string)), Expression.Property(parameterExpression, methodof(Student.get_Name())))
 }), new ParameterExpression[]
 {
  parameterExpression
 });
 Func<Student, StudentSecond> f = ss.Compile();
 StudentSecond studentSecond = f(s);

那么也就是说我们只要用反射循环所有的属性然后Expression.Bind所有的属性。最后调用Compile()(s)就可以获取正确的StudentSecond。

看到这有的人又要问了,如果用反射的话那岂不是效率很低,和直接用反射或者用序列化没什么区别吗?

当然这个可以解决的,就是我们的表达式树可以缓存。只是第一次用的时候需要反射,以后再用就不需要反射了。

3.3、复制对象通用代码

为了通用性所以其中的Student和StudentSecond分别泛型替换。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private static Dictionary<string, object> _Dic = new Dictionary<string, object>();
 
  private static TOut TransExp<TIn, TOut>(TIn tIn)
  {
   string key = string.Format("trans_exp_{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])(tIn);
  }

调用:StudentSecond ss= TransExp<Student, StudentSecond>(s);

调用一百万次耗时:564毫秒

4、总结

从以上的测试和分析可以很容易得出,用表达式树是可以达到效率与书写方式二者兼备的方法之一,总之比传统的序列化和反射更加优秀。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持服务器之家!

原文链接:http://www.cnblogs.com/emrys5/p/expression_trans_model.html

延伸 · 阅读

精彩推荐
  • C#c#学习之30分钟学会XAML

    c#学习之30分钟学会XAML

    一个界面程序的核心,无疑就是界面和后台代码,而xaml就是微软为构建应用程序界面而创建的一种描述性语言,也就是说,这东西是搞界面的...

    C#教程网8812021-12-10
  • C#C#直线的最小二乘法线性回归运算实例

    C#直线的最小二乘法线性回归运算实例

    这篇文章主要介绍了C#直线的最小二乘法线性回归运算方法,实例分析了给定一组点,用最小二乘法进行线性回归运算的实现技巧,具有一定参考借鉴价值,需要...

    北风其凉8912021-10-18
  • C#浅谈C# winForm 窗体闪烁的问题

    浅谈C# winForm 窗体闪烁的问题

    下面小编就为大家带来一篇浅谈C# winForm 窗体闪烁的问题。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    C#教程网7962021-12-21
  • C#Unity3D UGUI实现缩放循环拖动卡牌展示效果

    Unity3D UGUI实现缩放循环拖动卡牌展示效果

    这篇文章主要为大家详细介绍了Unity3D UGUI实现缩放循环拖动展示卡牌效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参...

    诗远3662022-03-11
  • C#C# 后台处理图片的几种方法

    C# 后台处理图片的几种方法

    本篇文章主要介绍了C# 后台处理图片的几种方法,非常具有实用价值,需要的朋友可以参考下。...

    IT小伙儿10162021-12-08
  • C#C#实现的文件操作封装类完整实例【删除,移动,复制,重命名】

    C#实现的文件操作封装类完整实例【删除,移动,复制,重命名】

    这篇文章主要介绍了C#实现的文件操作封装类,结合完整实例形式分析了C#封装文件的删除,移动,复制,重命名等操作相关实现技巧,需要的朋友可以参考下...

    Rising_Sun3892021-12-28
  • C#聊一聊C#接口问题 新手速来围观

    聊一聊C#接口问题 新手速来围观

    聊一聊C#接口问题,新手速来围观,一个通俗易懂的例子帮助大家更好的理解C#接口问题,感兴趣的小伙伴们可以参考一下...

    zenkey7072021-12-03
  • C#C#基础之泛型

    C#基础之泛型

    泛型是 2.0 版 C# 语言和公共语言运行库 (CLR) 中的一个新功能。接下来通过本文给大家介绍c#基础之泛型,感兴趣的朋友一起学习吧...

    方小白7732021-12-03