<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
之前學習了設計模式原型模式,在原型模式中就提到了物件的深拷貝。深拷貝指的是拷貝一個物件時,不僅僅把物件的參照進行復制,還把該物件參照的值也一起拷貝。與淺拷貝不同的就是,深拷貝後的拷貝物件就和源物件互相獨立,其中任何一個物件的改動都不會對另外一個物件造成影響。
在查詢資料之後,探究了以下幾種C#物件深拷貝方式,同時簡單對比了以下列出的幾種深拷貝方式的速度(簡單測試,僅測試物件深拷貝速度,不考慮效能影響)。
測試平臺:Intel 9700K+DDR4 3600 32G,框架為.NET 5.0。測試方式為建立100萬次,比較執行時間。拷貝的物件如下:
[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; } }
簡單物件建立,不考慮有建構函式的情況。
NewUserInfo newInfo = new NewUserInfo() { Name = info.Name, Age = info.Age, UserId = info.UserId, Address = info.Address, UpdateTime = info.UpdateTime, CreateTime = info.CreateTime, };
100萬次執行時間為39.4073ms,位居第一。當然,在這種不考慮建構函式的情況下,手寫建立肯定是最快的。但是同時,如果遇到複雜物件,程式碼量也是最多的。
這也是在日常程式碼中最常用的方式之一。
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; }
呼叫
NewUserInfo newInfo = TransReflection<UserInfo, NewUserInfo>(info);
100萬次執行時間為1618.4662ms,平均執行時間為0.001618,看起來還行。
使用System.Text.Json作為序列化和反序列化工具。
UserInfo newInfo = JsonSerializer.Deserialize<UserInfo>(JsonSerializer.Serialize(info));
100萬次執行時間為2222.2078ms,比反射慢一點點。
首先不推薦使用這種方式,一是BinaryFormatter.Serialize微軟已不推薦使用(據微軟官網檔案說是有漏洞,具體有什麼漏洞沒細究),二是必須在要序列化的物件上面寫上Serializable的關鍵字,三是速度並不理想。
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); } }
100萬次執行時間為8545.9835ms,講道理應該是比Json序列化要更快的,但是實際上慢了許多。
熟悉的AutoMapper,效能也沒有讓我們失望。
//迴圈外建立MapperConfig var config = new MapperConfiguration(cfg => cfg.CreateMap<UserInfo, UserInfo>()); var mapper = config.CreateMapper(); //迴圈內呼叫 UserInfo newInfo = mapper.Map<UserInfo>(info);
100萬次執行時間為267.5073ms,位居第三。
重頭戲來了,此處程式碼來源於文首中的部落格中,效能讓人大吃一驚。其原理是反射和表示式樹相結合,先用反射獲取欄位然後快取起來,再用表示式樹賦值。
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); } }
呼叫
UserInfo newInfo = TransExp<UserInfo, UserInfo>.Trans(info);
100萬次執行時間為77.3653ms,位居第二。僅比手寫慢一點點。
簡單整理成柱狀圖,可以很清晰的對比出這幾種深拷貝方式之間的速度差距。總結來說就是,一般簡單的物件深拷貝,推薦直接手寫,複雜物件深拷貝,推薦使用表示式樹。當然,如果建立物件中還涉及到建構函式初始化,那又是不同的情況,這裡暫不討論。
附上本次測試用的完整程式碼。
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 = "張三", Age = 18, UserId = Guid.NewGuid().ToString("N"), Address = "銀河系地球中國", 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++) { //手寫 39.4073ms //UserInfo newInfo = new UserInfo() //{ // Name = info.Name, // Age = info.Age, // UserId = info.UserId, // Address = info.Address, // UpdateTime = info.UpdateTime, // CreateTime = info.CreateTime, //}; //反射 1618.4662ms //UserInfo newInfo = TransReflection<UserInfo, UserInfo>(info); //Json字串序列化 2222.2078ms //UserInfo newInfo = JsonSerializer.Deserialize<UserInfo>(JsonSerializer.Serialize(info)); //物件二進位制序列化 8545.9835ms //UserInfo newInfo = ObjectMemoryConvert<UserInfo, UserInfo>(info); //表示式樹 77.3653ms //UserInfo newInfo = TransExp<UserInfo, UserInfo>.Trans(info); //AutoMapper 267.5073ms //UserInfo newInfo = mapper.Map<UserInfo>(info); } Console.WriteLine("總共花費{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; } } }
到此這篇關於C#深拷貝方法探究及效能比較的文章就介紹到這了,更多相關C#深拷貝方法內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45