我想做一些事情,比如:
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();
然后对未反映在原始对象中的新对象进行更改。
我通常不需要这个功能,所以当有必要时,我会创建一个新对象,然后单独复制每个属性,但这总是让我觉得有更好或更优雅的方法来处理这种情况。
如何克隆或深度复制对象,以便在原始对象中不反映任何更改的情况下修改克隆的对象?
当前回答
这个方法为我解决了问题:
private static MyObj DeepCopy(MyObj source)
{
var DeserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };
return JsonConvert.DeserializeObject<MyObj >(JsonConvert.SerializeObject(source), DeserializeSettings);
}
这样使用:MyObj a=DeepCopy(b);
其他回答
我对当前的答案做了一些基准测试,发现了一些有趣的事实。
使用BinarySerializer=>https://stackoverflow.com/a/78612/6338072
使用XmlSerializer=>https://stackoverflow.com/a/50150204/6338072
使用Activator.CreateInstance=>https://stackoverflow.com/a/56691124/6338072
这些是结果
BenchmarkDotNet=v0.13.1, OS=Windows 10.0.18363.1734 (1909/November2019Update/19H2)
Intel Core i5-6200U CPU 2.30GHz(Skylake),1个CPU,4个逻辑核和2个物理核[主机]:.NET Framework 4.8(4.8.4400.0),X86 LegacyJIT默认作业:.NET Framework 4.8(4.8.4400.0),X86 LegacyJIT
Method | Mean | Error | StdDev | Gen 0 | Allocated |
---|---|---|---|---|---|
BinarySerializer | 220.69 us | 4.374 us | 9.963 us | 49.8047 | 77 KB |
XmlSerializer | 182.72 us | 3.619 us | 9.405 us | 21.9727 | 34 KB |
Activator.CreateInstance | 49.99 us | 0.992 us | 2.861 us | 1.9531 | 3 KB |
如果您已经在使用第三方应用程序(如ValueInjector或Automapper),可以执行以下操作:
MyObject oldObj; // The existing object to clone
MyObject newObj = new MyObject();
newObj.InjectFrom(oldObj); // Using ValueInjecter syntax
使用此方法,您不必在对象上实现ISerializable或ICloneable。这在MVC/MVVM模式中很常见,因此已经创建了这样的简单工具。
请参阅GitHub上的ValueInjecter深度克隆示例。
由于我在不同的项目中找不到满足我所有需求的克隆器,我创建了一个可以配置并适应不同代码结构的深度克隆器,而不是调整我的代码以满足克隆器的需求。这是通过向要克隆的代码添加注释来实现的,或者您只需保留代码的默认行为。它使用反射、类型缓存并基于fasterflect。对于大量数据和高对象层次结构(与其他基于反射/序列化的算法相比),克隆过程非常快。
https://github.com/kalisohn/CloneBehave
也可作为nuget包提供:https://www.nuget.org/packages/Clone.Behave/1.0.0
例如:以下代码将执行deepCloneAddress,但只执行_currentJob字段的浅拷贝。
public class Person
{
[DeepClone(DeepCloneBehavior.Shallow)]
private Job _currentJob;
public string Name { get; set; }
public Job CurrentJob
{
get{ return _currentJob; }
set{ _currentJob = value; }
}
public Person Manager { get; set; }
}
public class Address
{
public Person PersonLivingHere { get; set; }
}
Address adr = new Address();
adr.PersonLivingHere = new Person("John");
adr.PersonLivingHere.BestFriend = new Person("James");
adr.PersonLivingHere.CurrentJob = new Job("Programmer");
Address adrClone = adr.Clone();
//RESULT
adr.PersonLivingHere == adrClone.PersonLivingHere //false
adr.PersonLivingHere.Manager == adrClone.PersonLivingHere.Manager //false
adr.PersonLivingHere.CurrentJob == adrClone.PersonLivingHere.CurrentJob //true
adr.PersonLivingHere.CurrentJob.AnyProperty == adrClone.PersonLivingHere.CurrentJob.AnyProperty //true
通用方法在技术上都是有效的,但我只想补充一点,因为我们实际上很少需要真正的深度复制,我强烈反对在实际的业务应用程序中使用通用深度复制,因为这使得您可能会在许多地方复制对象,然后显式修改,这很容易丢失。
在大多数实际情况下,您还希望对复制过程进行尽可能多的粒度控制,因为您不仅耦合到数据访问框架,而且在实践中,复制的业务对象几乎不应该100%相同。举一个ORM用来识别对象引用的referenceId的例子,一个完整的深度副本也会复制这个id,所以在内存中,对象会不同,一旦你将其提交到数据存储,它就会抱怨,因此,无论如何,您都必须在复制后手动修改此财产,如果对象发生更改,则需要在使用通用深度复制的所有位置对其进行调整。
用ICloneable扩展@cregox答案,到底什么是深度副本?它只是堆上新分配的一个对象,与原始对象相同,但占用不同的内存空间,而不是使用通用的克隆器功能,为什么不创建一个新对象呢?
我个人在我的域对象上使用静态工厂方法的思想。
例子:
public class Client
{
public string Name { get; set; }
protected Client()
{
}
public static Client Clone(Client copiedClient)
{
return new Client
{
Name = copiedClient.Name
};
}
}
public class Shop
{
public string Name { get; set; }
public string Address { get; set; }
public ICollection<Client> Clients { get; set; }
public static Shop Clone(Shop copiedShop, string newAddress, ICollection<Client> clients)
{
var copiedClients = new List<Client>();
foreach (var client in copiedShop.Clients)
{
copiedClients.Add(Client.Clone(client));
}
return new Shop
{
Name = copiedShop.Name,
Address = newAddress,
Clients = copiedClients
};
}
}
如果有人在研究如何在保持对复制过程的完全控制的同时构建对象实例化,这是我个人非常成功的解决方案。受保护的构造函数也使其成为可能,其他开发人员被迫使用工厂方法,该方法提供了一个简洁的单点对象实例化,封装了对象内部的构造逻辑。如果需要,您还可以重载该方法,并为不同的位置提供多个克隆逻辑。
我提出这一点是为了克服.NET的一个缺点,即必须手动深度复制List<T>。
我使用这个:
static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements)
{
foreach (SpotPlacement sp in spotPlacements)
{
yield return (SpotPlacement)sp.Clone();
}
}
在另一个地方:
public object Clone()
{
OrderItem newOrderItem = new OrderItem();
...
newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
...
return newOrderItem;
}
我试图提出一个这样做的oneliner,但这是不可能的,因为yield不能在匿名方法块中工作。
更好的是,使用通用的List<T>克隆器:
class Utility<T> where T : ICloneable
{
static public IEnumerable<T> CloneList(List<T> tl)
{
foreach (T t in tl)
{
yield return (T)t.Clone();
}
}
}