I learned the design pattern and prototype pattern before. In the prototype pattern, I mentioned the deep copy of objects. Deep copy refers to copying an object, not only the reference of the object, but also the value of the object reference. Different from shallow copy, the copy object after deep copy is independent of the source object, and the change of any object will not affect the other object.
After querying the data, we explored the following C# object deep copy methods, and briefly compared the speed of the following deep copy methods (simple test, only test the object deep copy speed, without considering the performance impact).
Test platform: Intel 9700K+DDR4 3600 32G, the framework is NET 5.0. The test method is to create 1 million times and compare the execution time. The copied objects are as follows:
[Serializable] class UserInfo { public string Name { get; set; } public string UserId { get; set; } public int Age { get; set; } public string Address { get; set; } public long UpdateTime { get; set; } public long CreateTime { get; set; } }
1. Create objects by handwriting
Simple object creation, regardless of the constructor.
NewUserInfo newInfo = new NewUserInfo() { Name = info.Name, Age = info.Age, UserId = info.UserId, Address = info.Address, UpdateTime = info.UpdateTime, CreateTime = info.CreateTime, };
The execution time of 1 million times is 39.4073ms, ranking first. Of course, handwriting creation is definitely the fastest without considering the constructor. But at the same time, if you encounter complex objects, the amount of code is also the largest.
2. Reflection
This is also one of the most commonly used methods in daily code.
private static TOut TransReflection<TIn, TOut>(TIn tIn) { TOut tOut = Activator.CreateInstance<TOut>(); var tInType = tIn.GetType(); foreach (var itemOut in tOut.GetType().GetProperties()) { var itemIn = tInType.GetProperty(itemOut.Name); ; if (itemIn != null) { itemOut.SetValue(tOut, itemIn.GetValue(tIn)); } } return tOut; }
call
NewUserInfo newInfo = TransReflection<UserInfo, NewUserInfo>(info);
The execution time of 1 million times is 1618.4662ms, and the average execution time is 0.001618. It looks ok.
3. Json string serialization
Use system Text. Jason acts as a serialization and deserialization tool.
UserInfo newInfo = JsonSerializer.Deserialize<UserInfo>(JsonSerializer.Serialize(info));
The execution time of 1million times is 2222.2078ms, which is a little slower than reflection.
4. Object binary serialization
First of all, this method is not recommended. One is BinaryFormatter Serialize is not recommended by Microsoft (according to the document on Microsoft's official website, there are loopholes, and the specific loopholes have not been studied in detail). Second, the keyword Serializable must be written on the object to be serialized. Third, the speed is not ideal.
private static TOut ObjectMemoryConvert<TIn, TOut>(TIn tIn) { using (MemoryStream ms = new MemoryStream()) { BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(ms, tIn); ms.Position = 0; return (TOut)formatter.Deserialize(ms); } }
The execution time of 1 million times is 8545.9835ms, which should be faster than Json serialization, but it is actually much slower.
5,AutoMapper
Familiar with AutoMapper, the performance did not disappoint us.
//Create MapperConfig out of loop var config = new MapperConfiguration(cfg => cfg.CreateMap<UserInfo, UserInfo>()); var mapper = config.CreateMapper(); //Call inside loop UserInfo newInfo = mapper.Map<UserInfo>(info);
The execution time of 1 million times is 267.5073 MS, ranking third.
6. Expression tree
Here comes the play. The code here comes from the blog at the beginning of the article, and the performance is amazing. Its principle is the combination of reflection and expression tree. First, the fields are obtained by reflection, then cached, and then assigned by expression tree.
public static class TransExp<TIn, TOut> { private static readonly Func<TIn, TOut> cache = GetFunc(); private static Func<TIn, TOut> GetFunc() { ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p"); List<MemberBinding> memberBindingList = new List<MemberBinding>(); foreach (var item in typeof(TOut).GetProperties()) { if (!item.CanWrite) continue; 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 }); return lambda.Compile(); } public static TOut Trans(TIn tIn) { return cache(tIn); } }
call
UserInfo newInfo = TransExp<UserInfo, UserInfo>.Trans(info);
The execution time of 1 million times is 77.3653ms, ranking the second. Only a little slower than handwriting.
Simply organize it into a bar chart, which can clearly compare the speed gap between these deep copy methods. To sum up, direct handwriting is recommended for simple object deep copy, and expression tree is recommended for complex object deep copy. Of course, if constructor initialization is also involved in creating objects, it is a different situation, which will not be discussed here.
The complete code for this test is attached.
using AutoMapper; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq.Expressions; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.Text.Json; using System.Threading.Tasks; namespace TestObjectDeepCopy { class Program { static void Main(string[] args) { UserInfo info = new UserInfo() { Name = "Zhang San", Age = 18, UserId = Guid.NewGuid().ToString("N"), Address = "Galaxy Earth China", UpdateTime = 1615888888, CreateTime = 1615895454, }; var config = new MapperConfiguration(cfg => cfg.CreateMap<UserInfo, UserInfo>()); var mapper = config.CreateMapper(); int count = 1000000; Stopwatch sw = new Stopwatch(); sw.Start(); for (int i = -0; i < count; i++) { //Handwriting 39.4073ms //UserInfo newInfo = new UserInfo() //{ // Name = info.Name, // Age = info.Age, // UserId = info.UserId, // Address = info.Address, // UpdateTime = info.UpdateTime, // CreateTime = info.CreateTime, //}; //Reflection 1618.4662ms //UserInfo newInfo = TransReflection<UserInfo, UserInfo>(info); //Json string serialization 2222.2078ms //UserInfo newInfo = JsonSerializer.Deserialize<UserInfo>(JsonSerializer.Serialize(info)); //Object binary serialization 8545.9835ms //UserInfo newInfo = ObjectMemoryConvert<UserInfo, UserInfo>(info); //Expression tree 77.3653ms //UserInfo newInfo = TransExp<UserInfo, UserInfo>.Trans(info); //AutoMapper 267.5073ms //UserInfo newInfo = mapper.Map<UserInfo>(info); } Console.WriteLine("Total cost{0}ms.", sw.Elapsed.TotalMilliseconds); sw.Stop(); Console.ReadKey(); } private static TOut TransReflection<TIn, TOut>(TIn tIn) { TOut tOut = Activator.CreateInstance<TOut>(); var tInType = tIn.GetType(); foreach (var itemOut in tOut.GetType().GetProperties()) { var itemIn = tInType.GetProperty(itemOut.Name); ; if (itemIn != null) { itemOut.SetValue(tOut, itemIn.GetValue(tIn)); } } return tOut; } private static TOut ObjectMemoryConvert<TIn, TOut>(TIn tIn) { using (MemoryStream ms = new MemoryStream()) { BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(ms, tIn); ms.Position = 0; return (TOut)formatter.Deserialize(ms); } } } public static class TransExp<TIn, TOut> { private static readonly Func<TIn, TOut> cache = GetFunc(); private static Func<TIn, TOut> GetFunc() { ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p"); List<MemberBinding> memberBindingList = new List<MemberBinding>(); foreach (var item in typeof(TOut).GetProperties()) { if (!item.CanWrite) continue; 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 }); return lambda.Compile(); } public static TOut Trans(TIn tIn) { return cache(tIn); } } [Serializable] class UserInfo { public string Name { get; set; } public string UserId { get; set; } public int Age { get; set; } public string Address { get; set; } public long UpdateTime { get; set; } public long CreateTime { get; set; } } }