我想做一些事情,比如:
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();
然后对未反映在原始对象中的新对象进行更改。
我通常不需要这个功能,所以当有必要时,我会创建一个新对象,然后单独复制每个属性,但这总是让我觉得有更好或更优雅的方法来处理这种情况。
如何克隆或深度复制对象,以便在原始对象中不反映任何更改的情况下修改克隆的对象?
当前回答
如果您已经在使用第三方应用程序(如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深度克隆示例。
其他回答
映射器执行深度复制。为对象的每个成员创建一个新对象并分配其所有值。它递归地处理每个非基元内部成员。
我建议您选择目前发展最快、最活跃的产品之一。我建议使用UltraMapperhttps://github.com/maurosampietro/UltraMapper
Nuget软件包:https://www.nuget.org/packages/UltraMapper/
基于@craastad的答案,对于派生类。
在最初的答案中,如果调用者在基类对象上调用DeepCopy,则克隆的对象属于基类。但下面的代码将返回派生类。
using Newtonsoft.Json;
public static T DeepCopy<T>(this T source)
{
return (T)JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source), source.GetType());
}
不使用ICloneable的原因不是因为它没有通用接口。不使用它的原因是因为它很模糊。它不清楚你得到的是浅拷贝还是深拷贝;这取决于实施者。
是的,MemberwiseColone生成一个浅拷贝,但MemberwiseClone的反面不是克隆;可能是DeepClone,但它并不存在。当您通过对象的ICloneable接口使用对象时,您无法知道底层对象执行哪种克隆。(而且XML注释不会很清楚,因为您将得到接口注释,而不是对象的Clone方法上的注释。)
我通常只做一个Copy方法,它完全符合我的要求。
深度克隆就是复制状态。对于.net state,表示字段。
假设有一个层次结构:
static class RandomHelper
{
private static readonly Random random = new Random();
public static int Next(int maxValue) => random.Next(maxValue);
}
class A
{
private readonly int random = RandomHelper.Next(100);
public override string ToString() => $"{typeof(A).Name}.{nameof(random)} = {random}";
}
class B : A
{
private readonly int random = RandomHelper.Next(100);
public override string ToString() => $"{typeof(B).Name}.{nameof(random)} = {random} {base.ToString()}";
}
class C : B
{
private readonly int random = RandomHelper.Next(100);
public override string ToString() => $"{typeof(C).Name}.{nameof(random)} = {random} {base.ToString()}";
}
克隆可以通过以下方式完成:
static class DeepCloneExtension
{
// consider instance fields, both public and non-public
private static readonly BindingFlags bindingFlags =
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
public static T DeepClone<T>(this T obj) where T : new()
{
var type = obj.GetType();
var result = (T)Activator.CreateInstance(type);
do
// copy all fields
foreach (var field in type.GetFields(bindingFlags))
field.SetValue(result, field.GetValue(obj));
// for every level of hierarchy
while ((type = type.BaseType) != typeof(object));
return result;
}
}
演示1:
Console.WriteLine(new C());
Console.WriteLine(new C());
var c = new C();
Console.WriteLine($"{Environment.NewLine}Image: {c}{Environment.NewLine}");
Console.WriteLine(new C());
Console.WriteLine(new C());
Console.WriteLine($"{Environment.NewLine}Clone: {c.DeepClone()}{Environment.NewLine}");
Console.WriteLine(new C());
Console.WriteLine(new C());
结果:
C.random = 92 B.random = 66 A.random = 71
C.random = 36 B.random = 64 A.random = 17
Image: C.random = 96 B.random = 18 A.random = 46
C.random = 60 B.random = 7 A.random = 37
C.random = 78 B.random = 11 A.random = 18
Clone: C.random = 96 B.random = 18 A.random = 46
C.random = 33 B.random = 63 A.random = 38
C.random = 4 B.random = 5 A.random = 79
注意,所有新对象的随机字段都有随机值,但克隆与图像完全匹配
演示2:
class D
{
public event EventHandler Event;
public void RaiseEvent() => Event?.Invoke(this, EventArgs.Empty);
}
// ...
var image = new D();
Console.WriteLine($"Created obj #{image.GetHashCode()}");
image.Event += (sender, e) => Console.WriteLine($"Event from obj #{sender.GetHashCode()}");
Console.WriteLine($"Subscribed to event of obj #{image.GetHashCode()}");
image.RaiseEvent();
image.RaiseEvent();
var clone = image.DeepClone();
Console.WriteLine($"obj #{image.GetHashCode()} cloned to obj #{clone.GetHashCode()}");
clone.RaiseEvent();
image.RaiseEvent();
结果:
Created obj #46104728
Subscribed to event of obj #46104728
Event from obj #46104728
Event from obj #46104728
obj #46104728 cloned to obj #12289376
Event from obj #12289376
Event from obj #46104728
注意,事件支持字段也被复制,客户端也订阅了克隆的事件。
代码生成器
我们已经看到了很多想法,从序列化到手动实现再到反射,我想使用CGbR代码生成器提出一种完全不同的方法。生成克隆方法具有内存和CPU效率,因此比标准DataContractSerializer快300倍。
你只需要一个带有ICloneable的部分类定义,生成器就可以完成剩下的工作:
public partial class Root : ICloneable
{
public Root(int number)
{
_number = number;
}
private int _number;
public Partial[] Partials { get; set; }
public IList<ulong> Numbers { get; set; }
public object Clone()
{
return Clone(true);
}
private Root()
{
}
}
public partial class Root
{
public Root Clone(bool deep)
{
var copy = new Root();
// All value types can be simply copied
copy._number = _number;
if (deep)
{
// In a deep clone the references are cloned
var tempPartials = new Partial[Partials.Length];
for (var i = 0; i < Partials.Length; i++)
{
var value = Partials[i];
value = value.Clone(true);
tempPartials[i] = value;
}
copy.Partials = tempPartials;
var tempNumbers = new List<ulong>(Numbers.Count);
for (var i = 0; i < Numbers.Count; i++)
{
var value = Numbers[i];
tempNumbers.Add(value);
}
copy.Numbers = tempNumbers;
}
else
{
// In a shallow clone only references are copied
copy.Partials = Partials;
copy.Numbers = Numbers;
}
return copy;
}
}
注意:最新版本有更多的空检查,但为了更好地理解,我省略了它们。