我想做一些事情,比如:
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();
然后对未反映在原始对象中的新对象进行更改。
我通常不需要这个功能,所以当有必要时,我会创建一个新对象,然后单独复制每个属性,但这总是让我觉得有更好或更优雅的方法来处理这种情况。
如何克隆或深度复制对象,以便在原始对象中不反映任何更改的情况下修改克隆的对象?
当前回答
找到了这个包,与之相比,DeepCloner似乎更快,而且没有依赖关系。
https://github.com/AlenToma/FastDeepCloner
其他回答
如果使用net.core并且对象是可序列化的,则可以使用
var jsonBin = BinaryData.FromObjectAsJson(yourObject);
then
var yourObjectCloned = jsonBin.ToObjectFromJson<YourType>();
BinaryData在dotnet中,因此您不需要第三方库。它还可以处理类上的属性为Object类型的情况(属性中的实际数据仍然需要可序列化)
我已经创建了一个可接受答案的版本,该版本同时适用于“[Serializable]”和“[DataContract]”。我写它已经有一段时间了,但如果我没有记错,[DataContract]需要一个不同的序列化程序。
需要System、System.IO、System.Runtime.Serialization、System.Runtime.Serialization.Formatters.Binary、System.Xml;
public static class ObjectCopier
{
/// <summary>
/// Perform a deep Copy of an object that is marked with '[Serializable]' or '[DataContract]'
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T Clone<T>(T source)
{
if (typeof(T).IsSerializable == true)
{
return CloneUsingSerializable<T>(source);
}
if (IsDataContract(typeof(T)) == true)
{
return CloneUsingDataContracts<T>(source);
}
throw new ArgumentException("The type must be Serializable or use DataContracts.", "source");
}
/// <summary>
/// Perform a deep Copy of an object that is marked with '[Serializable]'
/// </summary>
/// <remarks>
/// Found on http://stackoverflow.com/questions/78536/cloning-objects-in-c-sharp
/// Uses code found on CodeProject, which allows free use in third party apps
/// - http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
/// </remarks>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneUsingSerializable<T>(T source)
{
if (!typeof(T).IsSerializable)
{
throw new ArgumentException("The type must be serializable.", "source");
}
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
IFormatter formatter = new BinaryFormatter();
Stream stream = new MemoryStream();
using (stream)
{
formatter.Serialize(stream, source);
stream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(stream);
}
}
/// <summary>
/// Perform a deep Copy of an object that is marked with '[DataContract]'
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneUsingDataContracts<T>(T source)
{
if (IsDataContract(typeof(T)) == false)
{
throw new ArgumentException("The type must be a data contract.", "source");
}
// ** Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
DataContractSerializer dcs = new DataContractSerializer(typeof(T));
using(Stream stream = new MemoryStream())
{
using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(stream))
{
dcs.WriteObject(writer, source);
writer.Flush();
stream.Seek(0, SeekOrigin.Begin);
using (XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max))
{
return (T)dcs.ReadObject(reader);
}
}
}
}
/// <summary>
/// Helper function to check if a class is a [DataContract]
/// </summary>
/// <param name="type">The type of the object to check.</param>
/// <returns>Boolean flag indicating if the class is a DataContract (true) or not (false) </returns>
public static bool IsDataContract(Type type)
{
object[] attributes = type.GetCustomAttributes(typeof(DataContractAttribute), false);
return attributes.Length == 1;
}
}
我想你可以试试这个。
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = new MyObject(myObj); //DeepClone it
我刚刚创建了CloneExtensions库项目。它使用表达式树运行时代码编译生成的简单赋值操作执行快速、深度克隆。
如何使用它?
不用在字段和财产之间使用赋值基调编写自己的Clone或Copy方法,而是让程序使用Expression Tree自己完成。标记为扩展方法的GetClone<T>()方法允许您在实例上简单地调用它:
var newInstance = source.GetClone();
您可以使用CloningFlags枚举选择应该从源复制到newInstance的内容:
var newInstance
= source.GetClone(CloningFlags.Properties | CloningFlags.CollectionItems);
什么可以克隆?
原语(int、uint、byte、double、char等),已知不可变类型(DateTime、TimeSpan、String)和委托(包括动作、功能等)可为空T[]阵列自定义类和结构,包括泛型类和结构。
以下类/结构成员在内部克隆:
公共字段而非只读字段的值具有get和set访问器的公共财产的值实现ICollection的类型的集合项
它有多快?
该解决方案比反射更快,因为在GetClone<T>首次用于给定类型T之前,成员信息只能收集一次。
当您克隆多于两个相同类型T的实例时,它也比基于序列化的解决方案更快。
以及更多。。。
阅读文档中有关生成表达式的更多信息。
List<int>的示例表达式调试列表:
.Lambda #Lambda1<System.Func`4[System.Collections.Generic.List`1[System.Int32],CloneExtensions.CloningFlags,System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]],System.Collections.Generic.List`1[System.Int32]]>(
System.Collections.Generic.List`1[System.Int32] $source,
CloneExtensions.CloningFlags $flags,
System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]] $initializers) {
.Block(System.Collections.Generic.List`1[System.Int32] $target) {
.If ($source == null) {
.Return #Label1 { null }
} .Else {
.Default(System.Void)
};
.If (
.Call $initializers.ContainsKey(.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32]))
) {
$target = (System.Collections.Generic.List`1[System.Int32]).Call ($initializers.Item[.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32])]
).Invoke((System.Object)$source)
} .Else {
$target = .New System.Collections.Generic.List`1[System.Int32]()
};
.If (
((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)
) {
.Default(System.Void)
} .Else {
.Default(System.Void)
};
.If (
((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)
) {
.Block() {
$target.Capacity = .Call CloneExtensions.CloneFactory.GetClone(
$source.Capacity,
$flags,
$initializers)
}
} .Else {
.Default(System.Void)
};
.If (
((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)
) {
.Block(
System.Collections.Generic.IEnumerator`1[System.Int32] $var1,
System.Collections.Generic.ICollection`1[System.Int32] $var2) {
$var1 = (System.Collections.Generic.IEnumerator`1[System.Int32]).Call $source.GetEnumerator();
$var2 = (System.Collections.Generic.ICollection`1[System.Int32])$target;
.Loop {
.If (.Call $var1.MoveNext() != False) {
.Call $var2.Add(.Call CloneExtensions.CloneFactory.GetClone(
$var1.Current,
$flags,
$initializers))
} .Else {
.Break #Label2 { }
}
}
.LabelTarget #Label2:
}
} .Else {
.Default(System.Void)
};
.Label
$target
.LabelTarget #Label1:
}
}
以下c#代码具有相同的含义:
(source, flags, initializers) =>
{
if(source == null)
return null;
if(initializers.ContainsKey(typeof(List<int>))
target = (List<int>)initializers[typeof(List<int>)].Invoke((object)source);
else
target = new List<int>();
if((flags & CloningFlags.Properties) == CloningFlags.Properties)
{
target.Capacity = target.Capacity.GetClone(flags, initializers);
}
if((flags & CloningFlags.CollectionItems) == CloningFlags.CollectionItems)
{
var targetCollection = (ICollection<int>)target;
foreach(var item in (ICollection<int>)source)
{
targetCollection.Add(item.Clone(flags, initializers));
}
}
return target;
}
这不是很像您为List<int>编写自己的Clone方法吗?
我对当前的答案做了一些基准测试,发现了一些有趣的事实。
使用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 |