C Fast and Efficient Copy Objects (Expression Tree)

  • 2021-12-11 18:43:10
  • OfStack

1. Requirements

Often in your code, you have to copy an object once, or a value with the same property name once.

For example:


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" };

We need to assign a value to the new Student

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

Or assign a value to the attribute of another class StudentSecond, and the name and type 1 of the attribute of the two classes are the same.

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

2. Solutions

Of course, the most primitive way is to manually write all the attributes that need to be assigned. This efficiency is the highest. However, the repetition rate of such code is too high, and the code looks unsightly. More importantly, it is a waste of time. If a class has several 10 attributes, it is not a waste of energy to assign one attribute to another. Repeated labor work like this should need to be optimized.

2.1. Reflection

Reflection should be a method used by many people, that is, encapsulating a class, reflecting to get properties and set the values of properties.


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;
  }

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

1 million calls time: 2464 milliseconds

2.2. Serialization

There are many ways to serialize, such as binary, xml, json, etc. Today we will test it with json of Newtonsoft.

Call:


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

1 million calls time: 2984 milliseconds

It can be seen from this that there is little difference between serialization and reflection efficiency.

3. Expression tree

3.1. Introduction

If you don't know about expression tree, you can Baidu.

That is to say, copying objects can also be done in the form of expression trees.


  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);

In this way, we can achieve the same effect.

Some people say that this writing is no different from the original copy, but there are more codes. This is only the first step.

3.2. Analyze the code

We decompiled this expression code with ILSpy as follows:


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);

So that means we just loop all the attributes with reflection and then Expression. Bind all the attributes. Finally, you can get the correct StudentSecond by calling Compile () (s).

Seeing this, some people have to ask again, isn't it inefficient to use reflection, which is no different from using reflection or serialization directly?

Of course, this can be solved, that is, our expression tree can be cached. It only needs reflection when you use it for the first time, and you don't need reflection when you use it later.

3.3. Copy object common code

For generality, Student and StudentSecond are generic substitutions respectively.


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);
  }

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

1 million calls time: 564 milliseconds

4. Summary

From the above test and analysis, it can be easily concluded that using expression tree is one of the methods that can achieve both efficiency and writing style, which is better than traditional serialization and reflection.


Related articles: