欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-01-07 11:11:37
1、需求 在代码中经常会遇到需要把对象复制一遍,或者把属性名相同的值复制一遍。 比如: public class student { public...

1、需求

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

比如:

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、反射

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

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进行测试。

调用:

studentsecond ss= jsonconvert.deserializeobject<studentsecond>(jsonconvert.serializeobject(s));

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

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

3、表达式树

3.1、简介

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

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

  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反编译下这段表达式代码如下:

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分别泛型替换。

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、总结

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

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