我想做一些事情,比如:
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();
然后对未反映在原始对象中的新对象进行更改。
我通常不需要这个功能,所以当有必要时,我会创建一个新对象,然后单独复制每个属性,但这总是让我觉得有更好或更优雅的方法来处理这种情况。
如何克隆或深度复制对象,以便在原始对象中不反映任何更改的情况下修改克隆的对象?
我想做一些事情,比如:
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();
然后对未反映在原始对象中的新对象进行更改。
我通常不需要这个功能,所以当有必要时,我会创建一个新对象,然后单独复制每个属性,但这总是让我觉得有更好或更优雅的方法来处理这种情况。
如何克隆或深度复制对象,以便在原始对象中不反映任何更改的情况下修改克隆的对象?
当前回答
遵循以下步骤:
使用返回T的只读Self属性和ICloneable<out T>定义ISelf<T>,ICloneale<out T<派生自ISelf>并包含方法T Clone()。然后定义一个CloneBase类型,该类型实现了一个受保护的虚拟泛型VirtualClone,将MemberwiseClone转换为传入的类型。每个派生类型都应该通过调用基本克隆方法来实现VirtualClone,然后执行所需的操作来正确克隆父VirtualClone方法尚未处理的派生类型的那些方面。
为了实现最大的继承通用性,公开公共克隆功能的类应该是密封的,但派生自一个基类,该基类在其他方面是相同的,除非缺少克隆。不要传递显式可克隆类型的变量,而是采用ICloneable<theNonCloneableType>类型的参数。这将允许期望Foo的可克隆衍生物与DerivedFoo的可克隆衍生物一起工作的例程,但也允许创建Foo的不可克隆衍生物。
其他回答
DeepClonner:快速、简单、有效的NuGet包解决克隆问题
看完所有的答案后,我很惊讶没有人提到这个优秀的包:
DeepCloner GitHub项目
DeepCloner NuGet包
详细介绍一下它的README,下面是我们选择它的原因:
它可以深层或浅层复制在深度克隆中,维护所有对象图。在运行时使用代码生成,因为结果克隆非常快由内部结构复制的对象,未调用方法或构造函数您不需要以某种方式标记类(如Serializable属性或实现接口)无需为克隆指定对象类型。对象可以被强制转换为接口或抽象对象(例如,您可以将int数组克隆为抽象array或IEnumerable;甚至可以克隆null而不出错)克隆的对象没有任何能力确定他是克隆的(除非使用非常特定的方法)
用法:
var deepClone = new { Id = 1, Name = "222" }.DeepClone();
var shallowClone = new { Id = 1, Name = "222" }.ShallowClone();
性能:
README包含各种克隆库和方法的性能比较:DeepCloner性能。
要求:
.NET 4.0或更高版本或.NET Standard 1.3(.NET Core)需要完全信任权限集或反射权限(MemberAccess)
我喜欢这样的Copyconstructors:
public AnyObject(AnyObject anyObject)
{
foreach (var property in typeof(AnyObject).GetProperties())
{
property.SetValue(this, property.GetValue(anyObject));
}
foreach (var field in typeof(AnyObject).GetFields())
{
field.SetValue(this, field.GetValue(anyObject));
}
}
如果您有更多内容要复制,请添加它们
免责声明:我是上述包裹的作者。
我很惊讶2019年这个问题的顶级答案仍然使用序列化或反射。
序列化是有限的(需要属性、特定构造函数等),而且非常慢
BinaryFormatter需要Serializable属性,JsonConverter需要无参数构造函数或属性,既不能很好地处理只读字段或接口,两者都比需要的慢10-30x。
表达式树
您可以改为使用表达式树或Reflection.Emit仅生成一次克隆代码,然后使用编译后的代码,而不是缓慢的反射或序列化。
我自己遇到了这个问题,但没有找到令人满意的解决方案,因此我决定创建一个包,它可以做到这一点,并且可以处理所有类型的代码,而且速度几乎与自定义编写的代码一样快。
您可以在GitHub上找到该项目:https://github.com/marcelltoth/ObjectCloner
用法
您可以从NuGet安装它。获取ObjectCloner包并将其用作:
var clone = ObjectCloner.DeepClone(original);
或者如果您不介意用扩展名污染对象类型,请获取ObjectCloner.extensions,并写下:
var clone = original.DeepClone();
表演
克隆类层次结构的简单基准测试显示,性能比使用Reflection快约3倍,比Newtonsoft.Json序列化快约12倍,比强烈建议的BinaryFormatter快约36倍。
保持简单,并使用AutoMapper,正如其他人提到的,这是一个简单的小库,可以将一个对象映射到另一个对象。。。要将一个对象复制到另一个具有相同类型的对象,只需要三行代码:
MyType source = new MyType();
Mapper.CreateMap<MyType, MyType>();
MyType target = Mapper.Map<MyType, MyType>(source);
目标对象现在是源对象的副本。不够简单?创建扩展方法以在解决方案中的任何地方使用:
public static T Copy<T>(this T source)
{
T copy = default(T);
Mapper.CreateMap<T, T>();
copy = Mapper.Map<T, T>(source);
return copy;
}
扩展方法可以如下使用:
MyType copy = source.Copy();
这将把一个对象的所有可读写财产复制到另一个对象。
public class PropertyCopy<TSource, TTarget>
where TSource: class, new()
where TTarget: class, new()
{
public static TTarget Copy(TSource src, TTarget trg, params string[] properties)
{
if (src==null) return trg;
if (trg == null) trg = new TTarget();
var fulllist = src.GetType().GetProperties().Where(c => c.CanWrite && c.CanRead).ToList();
if (properties != null && properties.Count() > 0)
fulllist = fulllist.Where(c => properties.Contains(c.Name)).ToList();
if (fulllist == null || fulllist.Count() == 0) return trg;
fulllist.ForEach(c =>
{
c.SetValue(trg, c.GetValue(src));
});
return trg;
}
}
这是你使用它的方式:
var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave,
"Creation",
"Description",
"IdTicketStatus",
"IdUserCreated",
"IdUserInCharge",
"IdUserRequested",
"IsUniqueTicketGenerated",
"LastEdit",
"Subject",
"UniqeTicketRequestId",
"Visibility");
或复制所有内容:
var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave);