我想做一些事情,比如:
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();
然后对未反映在原始对象中的新对象进行更改。
我通常不需要这个功能,所以当有必要时,我会创建一个新对象,然后单独复制每个属性,但这总是让我觉得有更好或更优雅的方法来处理这种情况。
如何克隆或深度复制对象,以便在原始对象中不反映任何更改的情况下修改克隆的对象?
当前回答
另一个JSON.NET答案。此版本适用于不实现ISerializable的类。
public static class Cloner
{
public static T Clone<T>(T source)
{
if (ReferenceEquals(source, null))
return default(T);
var settings = new JsonSerializerSettings { ContractResolver = new ContractResolver() };
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source, settings), settings);
}
class ContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(p => base.CreateProperty(p, memberSerialization))
.Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(f => base.CreateProperty(f, memberSerialization)))
.ToList();
props.ForEach(p => { p.Writable = true; p.Readable = true; });
return props;
}
}
}
其他回答
如果使用net.core并且对象是可序列化的,则可以使用
var jsonBin = BinaryData.FromObjectAsJson(yourObject);
then
var yourObjectCloned = jsonBin.ToObjectFromJson<YourType>();
BinaryData在dotnet中,因此您不需要第三方库。它还可以处理类上的属性为Object类型的情况(属性中的实际数据仍然需要可序列化)
遵循以下步骤:
使用返回T的只读Self属性和ICloneable<out T>定义ISelf<T>,ICloneale<out T<派生自ISelf>并包含方法T Clone()。然后定义一个CloneBase类型,该类型实现了一个受保护的虚拟泛型VirtualClone,将MemberwiseClone转换为传入的类型。每个派生类型都应该通过调用基本克隆方法来实现VirtualClone,然后执行所需的操作来正确克隆父VirtualClone方法尚未处理的派生类型的那些方面。
为了实现最大的继承通用性,公开公共克隆功能的类应该是密封的,但派生自一个基类,该基类在其他方面是相同的,除非缺少克隆。不要传递显式可克隆类型的变量,而是采用ICloneable<theNonCloneableType>类型的参数。这将允许期望Foo的可克隆衍生物与DerivedFoo的可克隆衍生物一起工作的例程,但也允许创建Foo的不可克隆衍生物。
我对当前的答案做了一些基准测试,发现了一些有趣的事实。
使用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 |
我提出这一点是为了克服.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();
}
}
}
使用IClonable接口可以花费多少精力是令人难以置信的,尤其是当您有大量的类层次结构时。MemberwiseColone的工作方式也很奇怪——它甚至不能完全克隆普通的List类型的结构。
当然,串行化最有趣的困境是串行化反向引用——例如,具有子-父关系的类层次结构。我怀疑二进制序列化程序能否在这种情况下帮助您。(最终将导致递归循环+堆栈溢出)。
不知怎么的,我喜欢这里提出的解决方案:如何在.NET(特别是C#)中对对象进行深度复制?
然而,它不支持Lists,并补充说,该支持还考虑到了重新养育子女的问题。对于我制定的仅为父项的规则,该字段或属性应命名为“parent”,则DeepClone将忽略它。您可能需要决定自己的反向引用规则——对于树层次结构,它可能是“左/右”等。。。
以下是包含测试代码的完整代码片段:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
namespace TestDeepClone
{
class Program
{
static void Main(string[] args)
{
A a = new A();
a.name = "main_A";
a.b_list.Add(new B(a) { name = "b1" });
a.b_list.Add(new B(a) { name = "b2" });
A a2 = (A)a.DeepClone();
a2.name = "second_A";
// Perform re-parenting manually after deep copy.
foreach( var b in a2.b_list )
b.parent = a2;
Debug.WriteLine("ok");
}
}
public class A
{
public String name = "one";
public List<String> list = new List<string>();
public List<String> null_list;
public List<B> b_list = new List<B>();
private int private_pleaseCopyMeAsWell = 5;
public override string ToString()
{
return "A(" + name + ")";
}
}
public class B
{
public B() { }
public B(A _parent) { parent = _parent; }
public A parent;
public String name = "two";
}
public static class ReflectionEx
{
public static Type GetUnderlyingType(this MemberInfo member)
{
Type type;
switch (member.MemberType)
{
case MemberTypes.Field:
type = ((FieldInfo)member).FieldType;
break;
case MemberTypes.Property:
type = ((PropertyInfo)member).PropertyType;
break;
case MemberTypes.Event:
type = ((EventInfo)member).EventHandlerType;
break;
default:
throw new ArgumentException("member must be if type FieldInfo, PropertyInfo or EventInfo", "member");
}
return Nullable.GetUnderlyingType(type) ?? type;
}
/// <summary>
/// Gets fields and properties into one array.
/// Order of properties / fields will be preserved in order of appearance in class / struct. (MetadataToken is used for sorting such cases)
/// </summary>
/// <param name="type">Type from which to get</param>
/// <returns>array of fields and properties</returns>
public static MemberInfo[] GetFieldsAndProperties(this Type type)
{
List<MemberInfo> fps = new List<MemberInfo>();
fps.AddRange(type.GetFields());
fps.AddRange(type.GetProperties());
fps = fps.OrderBy(x => x.MetadataToken).ToList();
return fps.ToArray();
}
public static object GetValue(this MemberInfo member, object target)
{
if (member is PropertyInfo)
{
return (member as PropertyInfo).GetValue(target, null);
}
else if (member is FieldInfo)
{
return (member as FieldInfo).GetValue(target);
}
else
{
throw new Exception("member must be either PropertyInfo or FieldInfo");
}
}
public static void SetValue(this MemberInfo member, object target, object value)
{
if (member is PropertyInfo)
{
(member as PropertyInfo).SetValue(target, value, null);
}
else if (member is FieldInfo)
{
(member as FieldInfo).SetValue(target, value);
}
else
{
throw new Exception("destinationMember must be either PropertyInfo or FieldInfo");
}
}
/// <summary>
/// Deep clones specific object.
/// Analogue can be found here: https://stackoverflow.com/questions/129389/how-do-you-do-a-deep-copy-an-object-in-net-c-specifically
/// This is now improved version (list support added)
/// </summary>
/// <param name="obj">object to be cloned</param>
/// <returns>full copy of object.</returns>
public static object DeepClone(this object obj)
{
if (obj == null)
return null;
Type type = obj.GetType();
if (obj is IList)
{
IList list = ((IList)obj);
IList newlist = (IList)Activator.CreateInstance(obj.GetType(), list.Count);
foreach (object elem in list)
newlist.Add(DeepClone(elem));
return newlist;
} //if
if (type.IsValueType || type == typeof(string))
{
return obj;
}
else if (type.IsArray)
{
Type elementType = Type.GetType(type.FullName.Replace("[]", string.Empty));
var array = obj as Array;
Array copied = Array.CreateInstance(elementType, array.Length);
for (int i = 0; i < array.Length; i++)
copied.SetValue(DeepClone(array.GetValue(i)), i);
return Convert.ChangeType(copied, obj.GetType());
}
else if (type.IsClass)
{
object toret = Activator.CreateInstance(obj.GetType());
MemberInfo[] fields = type.GetFieldsAndProperties();
foreach (MemberInfo field in fields)
{
// Don't clone parent back-reference classes. (Using special kind of naming 'parent'
// to indicate child's parent class.
if (field.Name == "parent")
{
continue;
}
object fieldValue = field.GetValue(obj);
if (fieldValue == null)
continue;
field.SetValue(toret, DeepClone(fieldValue));
}
return toret;
}
else
{
// Don't know that type, don't know how to clone it.
if (Debugger.IsAttached)
Debugger.Break();
return null;
}
} //DeepClone
}
}