- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
在实际应用中经常会有这样的需求:获取一个与原对象数据相同但是独立于原对象的精准副本,简单来说就是克隆一份,拷贝一份,复制一份和原对象一样的对象,但是两者各种修改不能互相影响。这一行为也叫深克隆,深拷贝.
在C#里拷贝对象是一个看似简单实则相当复杂的事情,因此我不建议自己去做封装方法然后项目上使用的,这里面坑太多,容易出问题。下面给大家分享五大类N种深拷贝方法.
这类方法只对简单引用类型有效,如果类型中包含引用类型的属性字段,则无效.
MemberwiseClone是创建当前对象的一个浅拷贝。本质上来说它不是适合做深拷贝,但是如果对于一些简单引用类型即类型里面不包含引用类型属性字段,则可以使用此方法进行深拷贝。因为此方法是Obejct类型的受保护方法,因此只能在类的内部使用.
示例代码如下:
public class MemberwiseCloneModel
{
public int Age { get; set; }
public string Name { get; set; }
public MemberwiseCloneModel Clone()
{
return (MemberwiseCloneModel)this.MemberwiseClone();
}
}
public static void NativeMemberwiseClone()
{
var original = new MemberwiseCloneModel();
var clone = original.Clone();
Console.WriteLine(original == clone);
Console.WriteLine(ReferenceEquals(original, clone));
}
可能大多数人刚看到with表达式还一头雾水,这个和深拷贝有什么关系呢?它和record有关,record是在C# 9引入的当时还只能通过record struct声明值类型记录,在C# 10版本引入了record class可以声明引用类型记录。可能还是有不少人对record不是很了解,简单来说就是用于定义不可变的数据对象,是一个特殊的类型.
with可以应用于记录实例右侧来创建一个新的记录实例,此方式和MemberwiseClone有同样的问题,如果对象里面包含引用类型属性成员则只复制其属性。因此只能对简单的引用类型进行深拷贝。示例代码如下:
public record class RecordWithModel
{
public int Age { get; set; }
public string Name { get; set; }
}
public static void NativeRecordWith()
{
var original = new RecordWithModel();
var clone = original with { };
Console.WriteLine(original == clone);
Console.WriteLine(ReferenceEquals(original, clone));
}
这类方法都是需要手动处理的,简单又复杂.
纯手工就是属性字段一个一个赋值,说实话我最喜欢这种方式,整个过程完全可控,排查问题十分方便一目了然,当然如果遇到复杂的多次嵌套类型也是很头疼的。看下代码感受一下.
public class CloneModel
{
public int Age { get; set; }
public string Name { get; set; }
public List<CloneModel> Models { get; set; }
}
public static void ManualPure()
{
var original = new CloneModel
{
Models = new List<CloneModel>
{
new()
{
Age= 1,
Name="1"
}
}
};
var clone = new CloneModel
{
Age = original.Age,
Name = original.Name,
Models = original.Models.Select(x => new CloneModel
{
Age = x.Age,
Name = x.Name,
}).ToList()
};
Console.WriteLine(original == clone);
Console.WriteLine(ReferenceEquals(original, clone));
}
首先这是内置接口,也仅仅是定义了接口,具体实现还是需要靠自己实现,所以理论上和纯手工一样的,可以唯一的好处就是有一个统一定义,具体实现看完这篇文章都可以用来实现这个接口,这里就不在赘述了.
这类方法核心思想就是先序列化再反序列化,这里面也可以分为三小类:二进制类、Xml类、Json类.
从.NET5开始此方法已经标为弃用,大家可以忽略这个方案了,在这里给大家提个醒,对于老的项目可以参考下面代码.
public static T SerializeByBinary<T>(T original)
{
using (var memoryStream = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(memoryStream, original);
memoryStream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(memoryStream);
}
}
需要安装MessagePack包。实现如下:
public static T SerializeByMessagePack<T>(T original)
{
var bytes = MessagePackSerializer.Serialize(original);
return MessagePackSerializer.Deserialize<T>(bytes);
}
对象和成员需要使用[DataContract] 和 [DataMember] 属性定义,示例代码如下:
[DataContract]
public class DataContractModel
{
[DataMember]
public int Age { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public List<DataContractModel> Models { get; set; }
}
public static T SerializeByDataContract<T>(T original)
{
using var stream = new MemoryStream();
var serializer = new DataContractSerializer(typeof(T));
serializer.WriteObject(stream, original);
stream.Position = 0;
return (T)serializer.ReadObject(stream);
}
public static T SerializeByXml<T>(T original)
{
using (var ms = new MemoryStream())
{
XmlSerializer s = new XmlSerializer(typeof(T));
s.Serialize(ms, original);
ms.Position = 0;
return (T)s.Deserialize(ms);
}
}
目前有两个有名的Json序列化器:微软自家的System.Text.Json和Newtonsoft.Json(需安装库).
public static T SerializeByTextJson<T>(T original)
{
var json = System.Text.Json.JsonSerializer.Serialize(original);
return System.Text.Json.JsonSerializer.Deserialize<T>(json);
}
public static T SerializeByJsonNet<T>(T original)
{
var json = Newtonsoft.Json.JsonConvert.SerializeObject(original);
return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(json);
}
这类方法使用简单,方案成熟,比较适合项目上使用.
安装AutoMapper库 。
public static T ThirdPartyByAutomapper<T>(T original)
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<T, T>();
});
var mapper = config.CreateMapper();
T clone = mapper.Map<T, T>(original);
return clone;
}
安装DeepCloner库 。
public static T ThirdPartyByDeepCloner<T>(T original)
{
return original.DeepClone();
}
安装FastDeepCloner库 。
public static T ThirdPartyByFastDeepCloner<T>(T original)
{
return (T)DeepCloner.Clone(original);
}
这类方法都是半成品方法,仅供参考,提供思路,扩展视野,不适合项目使用,当然你可以把它们完善,各种特殊情况问题都处理好也是可以在项目上使用的.
比如下面没有处理字典、元组等类型,还有一些其他特殊情况.
public static T Reflection<T>(T original)
{
var type = original.GetType();
//如果是值类型、字符串或枚举,直接返回
if (type.IsValueType || type.IsEnum || original is string)
{
return original;
}
//处理集合类型
if (typeof(IEnumerable).IsAssignableFrom(type))
{
var listType = typeof(List<>).MakeGenericType(type.GetGenericArguments()[0]);
var listClone = (IList)Activator.CreateInstance(listType);
foreach (var item in (IEnumerable)original)
{
listClone.Add(Reflection(item));
}
return (T)listClone;
}
//创建新对象
var clone = Activator.CreateInstance(type);
//处理字段
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
{
var fieldValue = field.GetValue(original);
if (fieldValue != null)
{
field.SetValue(clone, Reflection(fieldValue));
}
}
//处理属性
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
{
if (property.CanRead && property.CanWrite)
{
var propertyValue = property.GetValue(original);
if (propertyValue != null)
{
property.SetValue(clone, Reflection(propertyValue));
}
}
}
return (T)clone;
}
Emit的本质是用C#来编写IL代码,这些代码都是比较晦涩难懂,后面找机会单独讲解。另外这里加入了缓存机制,以提高效率.
public class DeepCopyILEmit<T>
{
private static Dictionary<Type, Func<T, T>> _cacheILEmit = new();
public static T ILEmit(T original)
{
var type = typeof(T);
if (!_cacheILEmit.TryGetValue(type, out var func))
{
var dymMethod = new DynamicMethod($"{type.Name}DoClone", type, new Type[] { type }, true);
var cInfo = type.GetConstructor(new Type[] { });
var generator = dymMethod.GetILGenerator();
var lbf = generator.DeclareLocal(type);
generator.Emit(OpCodes.Newobj, cInfo);
generator.Emit(OpCodes.Stloc_0);
foreach (FieldInfo field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
generator.Emit(OpCodes.Ldloc_0);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldfld, field);
generator.Emit(OpCodes.Stfld, field);
}
generator.Emit(OpCodes.Ldloc_0);
generator.Emit(OpCodes.Ret);
func = (Func<T, T>)dymMethod.CreateDelegate(typeof(Func<T, T>));
_cacheILEmit.Add(type, func);
}
return func(original);
}
}
表达式树是一种数据结构,在运行时会被编译成IL代码,同样的这些代码也是比较晦涩难懂,后面找机会单独讲解。另外这里也加入了缓存机制,以提高效率.
public class DeepCopyExpressionTree<T>
{
private static readonly Dictionary<Type, Func<T, T>> _cacheExpressionTree = new();
public static T ExpressionTree(T original)
{
var type = typeof(T);
if (!_cacheExpressionTree.TryGetValue(type, out var func))
{
var originalParam = Expression.Parameter(type, "original");
var clone = Expression.Variable(type, "clone");
var expressions = new List<Expression>();
expressions.Add(Expression.Assign(clone, Expression.New(type)));
foreach (var prop in type.GetProperties())
{
var originalProp = Expression.Property(originalParam, prop);
var cloneProp = Expression.Property(clone, prop);
expressions.Add(Expression.Assign(cloneProp, originalProp));
}
expressions.Add(clone);
var lambda = Expression.Lambda<Func<T, T>>(Expression.Block(new[] { clone }, expressions), originalParam);
func = lambda.Compile();
_cacheExpressionTree.Add(type, func);
}
return func(original);
}
}
最后我们对后面三类所有方法进行一次基准测试对比,每个方法分别执行三组测试,三组分别测试100、1000、10000个对象。测试模型为:
[DataContract]
[Serializable]
public class DataContractModel
{
[DataMember]
public int Age { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public List<DataContractModel> Models { get; set; }
}
其中Models包含两个元素。最后测试结果如下:
通过结果可以发现:[表达式树]和[Emit] > [AutoMapper]和[DeepCloner] > [MessagePack] > 其他 。
第一梯队:性能最好的是[表达式树]和[Emit],两者相差无几,根本原因因为最终都是IL代码,减少了各种反射导致的性能损失。因此如果你有极致的性能需求,可以基于这两种方案进行改进以满足自己的需求.
第二梯队:第三方库[AutoMapper]和[DeepCloner] 性能紧随其后,相对来说也不错,而且是成熟的库,因此如果项目上使用可以优先考虑.
第三梯队:[MessagePack]性能比第二梯队差了一倍,当然这个也需要安装第三方库.
第四梯队:[System.Text.Json]如果不想额外安装库,有没有很高的性能要求可以考虑使用微软自身的Json序列化工具.
其他方法就可以忽略不看了.
注:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。https://gitee.com/hugogoos/Planner 。
最后此篇关于C#|.netcore基础-深拷贝的五大类N种实现方式的文章就讲到这里了,如果你想了解更多关于C#|.netcore基础-深拷贝的五大类N种实现方式的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我有一个基类和两个派生类,我需要将一个指向派生类对象的指针复制到另一个类中,就像示例一样。 class Base { public: Base(const Base& other); } cl
考虑 Container 类,它主要存储 Box 对象的 unique_ptr vector ,并可以对它们执行一些计算。 class Container { private: std::
引用是指保存的值为对象的地址。在 Python 语言中,一个变量保存的值除了基本类型保存的是值外,其它都是引用,因此对于它们的使用就需要小心一些。下面举个例子: 问题描述:已知一个列表,求生成一个
我正在尝试实现 Bron-Kerbosch 算法,这是一种用于查找派系的递归算法。我设法达到了一个点,它返回了正确数量的派系,但是当我打印它们时,它们不正确 - 添加了额外的节点。我在这里遗漏了什么明
在评估中,我选择了选项LINE I 上的运行时错误。没有未定义行为这样的选项,尽管我认为这是正确的选择。 我不确定,但我认为评估有误。我编译并运行了该程序,它确实打印了 3, 9, 0, 2, 1,
在函数签名中通过 const 值传递参数是否有任何好处(或相反,成本)? 所以: void foo( size_t nValue ) { // ... 对比 void foo( const s
我为 answer to another question 写了一个 OutputIterator .在这里: #include using namespace std; template clas
我有一个由第三方生成的 dll,它具有某种内部数据结构,将其大小限制为 X 个元素。 所以基本上,它有一个以 X 为限制的队列。 据我所知,DLL 是每个进程的,但是是否可以多次加载 DLL?也许每个
假设我有以下两个数据结构: std::vector all_items; std::set bad_items; all_items vector 包含所有已知项和 bad_items vector
如何在不渲染 CGImage 的情况下从另一个 CIImage 复制一个 CIImage 最佳答案 CIImage *copiedImage = [originalImage copy]; 正如您在
我有一个名为 UINode 的 GUI,我想创建一个拷贝并只更改一些内容。该项目由 3 个基本线程组成。 PingThread、RosThread 和 GuiThread。我试图复制粘贴项目文件夹并将
Qt 新手。如果这个问题太幼稚,请多多包涵。在 Windows 操作系统环境中,我有 Qt 对话框框架应用程序,它具有“重复”- 按钮。在同一目录中,有 Qt 应用程序 - (一个带有关闭按钮的对话框
我正在尝试创建一个函数来复制我的卡片结构。我只需复制 cvalue 即可轻松开始。然而,我的 cvalue 没有复制,当应该读取 1000 时它仍然读取 5。 #include #include
string str1("someString"); string str2 = string(str1);//how many copies are made here //copy2 =
我希望了解 boost::bind 执行何种函数对象的内部拷贝。由于这些对象的构造函数似乎没有被调用,我推测这是一种“非常浅的复制”,所以我引入了动态内存分配来产生一些错误。但是,下面代码的运行时输出
我正在查看 http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c22-make-default-operations-consis
下面的类方法Augmented3dPoint::getWorldPoint()返回对其成员的引用 cv::Point3f world_point; class Augmented3dPoint { p
我需要通过 MyClass2 将用户定义的 lambda 传递给 MyClass1。我想确保只有一步,没有拷贝。下面的代码实现了吗?有没有更好的方法来做到这一点(比如使用编译器完成的隐式移动)? 注意
在我的数据库访问代码中,我想写一个方法: variant_t GetQueryRows (...) 我想这样调用它: const variant_t result = GetQueryRows (..
我有一个包含引用的类,例如: class A { A(B &b) : b(b) {} // constructor B &b; } 有时b必须是只读的,有时是可写的。当我创建一个 const A
我是一名优秀的程序员,十分优秀!