我想做一些事情,比如:
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();
然后对未反映在原始对象中的新对象进行更改。
我通常不需要这个功能,所以当有必要时,我会创建一个新对象,然后单独复制每个属性,但这总是让我觉得有更好或更优雅的方法来处理这种情况。
如何克隆或深度复制对象,以便在原始对象中不反映任何更改的情况下修改克隆的对象?
当前回答
除了这里的一些精彩答案外,您可以在C#9.0及更高版本中完成以下操作(假设您可以将类转换为记录):
record Record
{
public int Property1 { get; set; }
public string Property2 { get; set; }
}
然后,只需使用with运算符将一个对象的值复制到新对象。
var object1 = new Record()
{
Property1 = 1,
Property2 = "2"
};
var object2 = object1 with { };
// object2 now has Property1 = 1 & Property2 = "2"
我希望这有助于:)
其他回答
遵循以下步骤:
使用返回T的只读Self属性和ICloneable<out T>定义ISelf<T>,ICloneale<out T<派生自ISelf>并包含方法T Clone()。然后定义一个CloneBase类型,该类型实现了一个受保护的虚拟泛型VirtualClone,将MemberwiseClone转换为传入的类型。每个派生类型都应该通过调用基本克隆方法来实现VirtualClone,然后执行所需的操作来正确克隆父VirtualClone方法尚未处理的派生类型的那些方面。
为了实现最大的继承通用性,公开公共克隆功能的类应该是密封的,但派生自一个基类,该基类在其他方面是相同的,除非缺少克隆。不要传递显式可克隆类型的变量,而是采用ICloneable<theNonCloneableType>类型的参数。这将允许期望Foo的可克隆衍生物与DerivedFoo的可克隆衍生物一起工作的例程,但也允许创建Foo的不可克隆衍生物。
简单的答案是从ICloneable接口继承,然后实现.clone函数。克隆应该按成员进行复制,并对任何需要它的成员执行深度复制,然后返回结果对象。这是一个递归操作(它要求您要克隆的类的所有成员都是值类型或实现ICloneable,并且它们的成员是值类型或者实现IClonea,依此类推)。
有关使用ICloneable克隆的详细说明,请参阅本文。
长篇大论的答案是“视情况而定”。正如其他人所提到的,ICloneable不受泛型支持,需要对循环类引用进行特殊考虑,并且实际上被一些人视为.NET Framework中的“错误”。序列化方法取决于对象是否可序列化,而这些对象可能不可序列化,并且您可能无法控制。对于哪种做法是“最佳”做法,社会上仍有很多争论。事实上,没有一个解决方案是适用于所有情况的一刀切的最佳实践,就像ICloneable最初被解释的那样。
请参阅本开发者角文章,了解更多选项(归功于Ian)。
好的,这篇文章中有一些明显的反射示例,但反射通常很慢,直到你开始正确缓存它。
如果你能正确缓存它,那么它将以4.6s的速度深度克隆1000000个对象(由Watcher测量)。
static readonly Dictionary<Type, PropertyInfo[]> ProperyList = new Dictionary<Type, PropertyInfo[]>();
而不是获取缓存的财产或将新属性添加到字典并简单使用它们
foreach (var prop in propList)
{
var value = prop.GetValue(source, null);
prop.SetValue(copyInstance, value, null);
}
在另一个答案中查看我的帖子中的完整代码
https://stackoverflow.com/a/34365709/4711853
通用方法在技术上都是有效的,但我只想补充一点,因为我们实际上很少需要真正的深度复制,我强烈反对在实际的业务应用程序中使用通用深度复制,因为这使得您可能会在许多地方复制对象,然后显式修改,这很容易丢失。
在大多数实际情况下,您还希望对复制过程进行尽可能多的粒度控制,因为您不仅耦合到数据访问框架,而且在实践中,复制的业务对象几乎不应该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();
}
}
}