C#深拷貝方法探究及性能比較(多種深拷貝)
之前學(xué)習(xí)了設(shè)計(jì)模式原型模式,在原型模式中就提到了對(duì)象的深拷貝。深拷貝指的是拷貝一個(gè)對(duì)象時(shí),不僅僅把對(duì)象的引用進(jìn)行復(fù)制,還把該對(duì)象引用的值也一起拷貝。與淺拷貝不同的就是,深拷貝后的拷貝對(duì)象就和源對(duì)象互相獨(dú)立,其中任何一個(gè)對(duì)象的改動(dòng)都不會(huì)對(duì)另外一個(gè)對(duì)象造成影響。
在查詢資料之后,探究了以下幾種C#對(duì)象深拷貝方式,同時(shí)簡(jiǎn)單對(duì)比了以下列出的幾種深拷貝方式的速度(簡(jiǎn)單測(cè)試,僅測(cè)試對(duì)象深拷貝速度,不考慮性能影響)。
測(cè)試平臺(tái):Intel 9700K+DDR4 3600 32G,框架為.NET 5.0。測(cè)試方式為創(chuàng)建100萬次,比較執(zhí)行時(shí)間。拷貝的對(duì)象如下:
[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、手寫創(chuàng)建對(duì)象
簡(jiǎn)單對(duì)象創(chuàng)建,不考慮有構(gòu)造函數(shù)的情況。
NewUserInfo newInfo = new NewUserInfo()
{
Name = info.Name,
Age = info.Age,
UserId = info.UserId,
Address = info.Address,
UpdateTime = info.UpdateTime,
CreateTime = info.CreateTime,
};100萬次執(zhí)行時(shí)間為39.4073ms,位居第一。當(dāng)然,在這種不考慮構(gòu)造函數(shù)的情況下,手寫創(chuàng)建肯定是最快的。但是同時(shí),如果遇到復(fù)雜對(duì)象,代碼量也是最多的。
2、反射
這也是在日常代碼中最常用的方式之一。
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;
}調(diào)用
NewUserInfo newInfo = TransReflection<UserInfo, NewUserInfo>(info);
100萬次執(zhí)行時(shí)間為1618.4662ms,平均執(zhí)行時(shí)間為0.001618,看起來還行。
3、Json字符串序列化
使用System.Text.Json作為序列化和反序列化工具。
UserInfo newInfo = JsonSerializer.Deserialize<UserInfo>(JsonSerializer.Serialize(info));
100萬次執(zhí)行時(shí)間為2222.2078ms,比反射慢一點(diǎn)點(diǎn)。
4、對(duì)象二進(jìn)制序列化
首先不推薦使用這種方式,一是BinaryFormatter.Serialize微軟已不推薦使用(據(jù)微軟官網(wǎng)文檔說是有漏洞,具體有什么漏洞沒細(xì)究),二是必須在要序列化的對(duì)象上面寫上Serializable的關(guān)鍵字,三是速度并不理想。
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萬次執(zhí)行時(shí)間為8545.9835ms,講道理應(yīng)該是比Json序列化要更快的,但是實(shí)際上慢了許多。
5、AutoMapper
熟悉的AutoMapper,性能也沒有讓我們失望。
//循環(huán)外創(chuàng)建MapperConfig var config = new MapperConfiguration(cfg => cfg.CreateMap<UserInfo, UserInfo>()); var mapper = config.CreateMapper(); //循環(huán)內(nèi)調(diào)用 UserInfo newInfo = mapper.Map<UserInfo>(info);
100萬次執(zhí)行時(shí)間為267.5073ms,位居第三。
6、表達(dá)式樹
重頭戲來了,此處代碼來源于文首中的博客中,性能讓人大吃一驚。其原理是反射和表達(dá)式樹相結(jié)合,先用反射獲取字段然后緩存起來,再用表達(dá)式樹賦值。
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);
}
}調(diào)用
UserInfo newInfo = TransExp<UserInfo, UserInfo>.Trans(info);
100萬次執(zhí)行時(shí)間為77.3653ms,位居第二。僅比手寫慢一點(diǎn)點(diǎn)。
簡(jiǎn)單整理成柱狀圖,可以很清晰的對(duì)比出這幾種深拷貝方式之間的速度差距。總結(jié)來說就是,一般簡(jiǎn)單的對(duì)象深拷貝,推薦直接手寫,復(fù)雜對(duì)象深拷貝,推薦使用表達(dá)式樹。當(dāng)然,如果創(chuàng)建對(duì)象中還涉及到構(gòu)造函數(shù)初始化,那又是不同的情況,這里暫不討論。

附上本次測(cè)試用的完整代碼。
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 = "銀河系地球中國(guó)",
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));
//對(duì)象二進(jìn)制序列化 8545.9835ms
//UserInfo newInfo = ObjectMemoryConvert<UserInfo, UserInfo>(info);
//表達(dá)式樹 77.3653ms
//UserInfo newInfo = TransExp<UserInfo, UserInfo>.Trans(info);
//AutoMapper 267.5073ms
//UserInfo newInfo = mapper.Map<UserInfo>(info);
}
Console.WriteLine("總共花費(fèi){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; }
}
}到此這篇關(guān)于C#深拷貝方法探究及性能比較的文章就介紹到這了,更多相關(guān)C#深拷貝方法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#實(shí)現(xiàn)驗(yàn)證身份證是否合法的方法
這篇文章主要介紹了C#實(shí)現(xiàn)驗(yàn)證身份證是否合法的方法,實(shí)例分析了通過自定義函數(shù)實(shí)現(xiàn)針對(duì)身份證合法性驗(yàn)證的技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-03-03
微信公眾平臺(tái)開發(fā)教程(三) 基礎(chǔ)框架搭建
這篇文章主要介紹了微信公眾平臺(tái)開發(fā)教程(三) 基礎(chǔ)框架搭建,具有一定的參考價(jià)值,有需要的可以了解一下。2016-12-12
C#下實(shí)現(xiàn)創(chuàng)建和刪除目錄的實(shí)例代碼
這篇文章主要介紹了C#下實(shí)現(xiàn)創(chuàng)建和刪除目錄的方法,功能非常實(shí)用,需要的朋友可以參考下2014-08-08
C#實(shí)現(xiàn)泛型List分組輸出元素的方法
這篇文章主要介紹了C#實(shí)現(xiàn)泛型List分組輸出元素的方法,涉及C#針對(duì)List的遍歷、排序、輸出等相關(guān)操作技巧,需要的朋友可以參考下2017-12-12

