C#深拷贝方法探究及性能比较(多种深拷贝)
作者:SilverFoxWhy8588 发布时间:2022-08-30 18:17:02
之前学习了设计模式原型模式,在原型模式中就提到了对象的深拷贝。深拷贝指的是拷贝一个对象时,不仅仅把对象的引用进行复制,还把该对象引用的值也一起拷贝。与浅拷贝不同的就是,深拷贝后的拷贝对象就和源对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。
在查询资料之后,探究了以下几种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; }
}
1、手写创建对象
简单对象创建,不考虑有构造函数的情况。
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,位居第一。当然,在这种不考虑构造函数的情况下,手写创建肯定是最快的。但是同时,如果遇到复杂对象,代码量也是最多的。
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;
}
调用
NewUserInfo newInfo = TransReflection<UserInfo, NewUserInfo>(info);
100万次执行时间为1618.4662ms,平均执行时间为0.001618,看起来还行。
3、Json字符串序列化
使用System.Text.Json作为序列化和反序列化工具。
UserInfo newInfo = JsonSerializer.Deserialize<UserInfo>(JsonSerializer.Serialize(info));
100万次执行时间为2222.2078ms,比反射慢一点点。
4、对象二进制序列化
首先不推荐使用这种方式,一是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序列化要更快的,但是实际上慢了许多。
5、AutoMapper
熟悉的AutoMapper,性能也没有让我们失望。
//循环外创建MapperConfig
var config = new MapperConfiguration(cfg => cfg.CreateMap<UserInfo, UserInfo>());
var mapper = config.CreateMapper();
//循环内调用
UserInfo newInfo = mapper.Map<UserInfo>(info);
100万次执行时间为267.5073ms,位居第三。
6、表达式树
重头戏来了,此处代码来源于文首中的博客中,性能让人大吃一惊。其原理是反射和表达式树相结合,先用反射获取字段然后缓存起来,再用表达式树赋值。
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; }
}
}
来源:https://www.cnblogs.com/SF8588/p/16152078.html


猜你喜欢
- ReadWriteLock 和 ReentrantReadWriteLock介绍ReadWriteLock,顾名思义,是读写锁。它维护了一对
- 本文实例讲述了Android编程开发之在Canvas中利用Path绘制基本图形的方法。分享给大家供大家参考,具体如下:在Android中绘制
- springboot启动是通过一个main方法启动的,代码如下@SpringBootApplicationpublic class Appl
- 绝大部分知识与实例来自O'REILLY的《Java网络编程》(Java Network Programming,Fourth Edi
- 本文要解决在侧滑菜单右边加个文本框,并能实现文本的上下滑动和菜单的左右滚动。这里推荐可以好好看看android的触摸事件的分发机制,这里我就
- 这篇文章主要介绍了Spring Cloud Sleuth整合zipkin过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一
- java常量池是一个经久不衰的话题,也是面试官的最爱,题目花样百出,这次好好总结一下。理论先拙劣的表达一下jvm虚拟内存分布:程序计数器是j
- 描述符描述符是你添加到那些定义中来改变他们的意思的关键词。Java 语言有很多描述符,包括以下这些:可访问描述符不可访问描述符应用描述符,你
- 本文实例讲述了Android4.0平板开发之隐藏底部任务栏的方法。分享给大家供大家参考,具体如下:getWindow().getDecorV
- spring cloud 配置中心客户端启动先启动了配置中心,然后启动客户端,发现打印的日志是这样的2020-04-29 11:13:02.
- 本文实例为大家分享了Java多文件以ZIP压缩包导出的具体代码,供大家参考,具体内容如下1、使用java实现吧服务器的图片打包成一个zip格
- 本文实例为大家分享了java聊天工具的具体制作代码,供大家参考,具体内容如下首先建立一个工程,导入数据库驱动工程图解释一下 entity包是
- 本文实例讲述了Android中SeekBar和RatingBar用法。分享给大家供大家参考,具体如下:什么是SeekBar?可以拖动的进度条
- 今天做某度的笔试题遇到一个编程题需要用到字符串中的字符的即时改变。题中给出的一个String字符串。绞尽脑汁试图使用构建一个新的String
- 前言之所以会有这篇文章,是因为公司的开发环境比较老,寻找一些jar包的时候总是会纠结对应的编译版本,感觉很麻烦,所以写了一个工具类用于读取c
- 为了提升编译速度,这几天用上了 AS 3.0 和 Gradle 3.0 插件,不得不说不论是 AS 3.0,还是 Gradle 3.0 都变
- 第一次接触到随机数还是在c语言里面 使用的是 rand(); 但是重新执行一次的时候会发现,诶,居然和上一次执行的结果是一样的,因为没有初始
- 一个简单的网格布局activity_main.xml<?xml version="1.0" encoding=&q
- 1:首先。创建一个springboot项目,这里我使用以及构建好基本框架的脚手架,打开是这个样子:Result类:已经封装好了三种返回类型的
- 以下我们系统通过原理,过程等方便给大家深入的简介了Java NIO的函数机制以及用法等,学习下吧。前言本篇主要讲解Java中的IO机制分为两