如何在。net中对对象进行深度复制?

我想要一份真正的深度拷贝。在Java中,这很容易,但在c#中如何做到呢?

601669 次浏览

重要提示

BinaryFormatter已弃用,2023年11月后将不再在. net中使用。看到# EYZ1


我已经看到了一些不同的方法,但我使用一个通用的实用方法:

public static T DeepClone<T>(this T obj)
{
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;


return (T) formatter.Deserialize(ms);
}
}

注:

  • 你的类必须被标记为[Serializable]才能工作。

  • 您的源文件必须包括以下代码:

     using System.Runtime.Serialization.Formatters.Binary;
    using System.IO;
    

基于Kilhoffer的解决方案……

在c# 3.0中,你可以像下面这样创建一个扩展方法:

public static class ExtensionMethods
{
// Deep clone
public static T DeepClone<T>(this T a)
{
using (MemoryStream stream = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, a);
stream.Position = 0;
return (T) formatter.Deserialize(stream);
}
}
}

用DeepClone方法扩展任何标记为[Serializable]的类

MyClass copy = obj.DeepClone();

也许你只需要一个浅拷贝,在这种情况下使用Object.MemberWiseClone()

MemberWiseClone()的文档中有一些很好的策略来深度复制:-

http://msdn.microsoft.com/en-us/library/system.object.memberwiseclone.aspx

    public static object CopyObject(object input)
{
if (input != null)
{
object result = Activator.CreateInstance(input.GetType());
foreach (FieldInfo field in input.GetType().GetFields(Consts.AppConsts.FullBindingList))
{
if (field.FieldType.GetInterface("IList", false) == null)
{
field.SetValue(result, field.GetValue(input));
}
else
{
IList listObject = (IList)field.GetValue(result);
if (listObject != null)
{
foreach (object item in ((IList)field.GetValue(input)))
{
listObject.Add(CopyObject(item));
}
}
}
}
return result;
}
else
{
return null;
}
}

这种方法比BinarySerialization快几倍,而且不需要[Serializable]属性。

您可以使用Nested MemberwiseClone进行深度复制。它几乎与复制值结构体的速度相同,并且比(a)反射或(b)序列化快一个数量级(如本页的其他回答所述)。

注意,如果你使用用于深度复制的嵌套MemberwiseClone,你必须手动为类中的每个嵌套级别实现一个ShallowCopy,以及一个DeepCopy,调用所有所说的ShallowCopy方法来创建一个完整的克隆。这很简单:总共只有几行,请参阅下面的演示代码。

下面是显示相对性能差异的代码输出(深嵌套MemberwiseCopy为4.77秒,Serialization为39.93秒)。使用嵌套的MemberwiseCopy几乎和复制结构体一样快,而且复制结构体非常接近。net所能实现的理论最大速度,这可能非常接近C或c++中同样事情的速度(但必须运行一些等效的基准测试来验证这一说法)。

    Demo of shallow and deep copy, using classes and MemberwiseClone:
Create Bob
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Clone Bob >> BobsSon
Adjust BobsSon details
BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Elapsed time: 00:00:04.7795670,30000000
Demo of shallow and deep copy, using structs and value copying:
Create Bob
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Clone Bob >> BobsSon
Adjust BobsSon details:
BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Elapsed time: 00:00:01.0875454,30000000
Demo of deep copy, using class and serialize/deserialize:
Elapsed time: 00:00:39.9339425,30000000

为了理解如何使用MemberwiseCopy进行深度复制,下面是演示项目:

// Nested MemberwiseClone example.
// Added to demo how to deep copy a reference class.
[Serializable] // Not required if using MemberwiseClone, only used for speed comparison using serialization.
public class Person
{
public Person(int age, string description)
{
this.Age = age;
this.Purchase.Description = description;
}
[Serializable] // Not required if using MemberwiseClone
public class PurchaseType
{
public string Description;
public PurchaseType ShallowCopy()
{
return (PurchaseType)this.MemberwiseClone();
}
}
public PurchaseType Purchase = new PurchaseType();
public int Age;
// Add this if using nested MemberwiseClone.
// This is a class, which is a reference type, so cloning is more difficult.
public Person ShallowCopy()
{
return (Person)this.MemberwiseClone();
}
// Add this if using nested MemberwiseClone.
// This is a class, which is a reference type, so cloning is more difficult.
public Person DeepCopy()
{
// Clone the root ...
Person other = (Person) this.MemberwiseClone();
// ... then clone the nested class.
other.Purchase = this.Purchase.ShallowCopy();
return other;
}
}
// Added to demo how to copy a value struct (this is easy - a deep copy happens by default)
public struct PersonStruct
{
public PersonStruct(int age, string description)
{
this.Age = age;
this.Purchase.Description = description;
}
public struct PurchaseType
{
public string Description;
}
public PurchaseType Purchase;
public int Age;
// This is a struct, which is a value type, so everything is a clone by default.
public PersonStruct ShallowCopy()
{
return (PersonStruct)this;
}
// This is a struct, which is a value type, so everything is a clone by default.
public PersonStruct DeepCopy()
{
return (PersonStruct)this;
}
}
// Added only for a speed comparison.
public class MyDeepCopy
{
public static T DeepCopy<T>(T obj)
{
object result = null;
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
result = (T)formatter.Deserialize(ms);
ms.Close();
}
return (T)result;
}
}

然后,从main调用演示:

    void MyMain(string[] args)
{
{
Console.Write("Demo of shallow and deep copy, using classes and MemberwiseCopy:\n");
var Bob = new Person(30, "Lamborghini");
Console.Write("  Create Bob\n");
Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
Console.Write("  Clone Bob >> BobsSon\n");
var BobsSon = Bob.DeepCopy();
Console.Write("  Adjust BobsSon details\n");
BobsSon.Age = 2;
BobsSon.Purchase.Description = "Toy car";
Console.Write("    BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
Console.Write("  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
Debug.Assert(Bob.Age == 30);
Debug.Assert(Bob.Purchase.Description == "Lamborghini");
var sw = new Stopwatch();
sw.Start();
int total = 0;
for (int i = 0; i < 100000; i++)
{
var n = Bob.DeepCopy();
total += n.Age;
}
Console.Write("  Elapsed time: {0},{1}\n", sw.Elapsed, total);
}
{
Console.Write("Demo of shallow and deep copy, using structs:\n");
var Bob = new PersonStruct(30, "Lamborghini");
Console.Write("  Create Bob\n");
Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
Console.Write("  Clone Bob >> BobsSon\n");
var BobsSon = Bob.DeepCopy();
Console.Write("  Adjust BobsSon details:\n");
BobsSon.Age = 2;
BobsSon.Purchase.Description = "Toy car";
Console.Write("    BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
Console.Write("  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
Debug.Assert(Bob.Age == 30);
Debug.Assert(Bob.Purchase.Description == "Lamborghini");
var sw = new Stopwatch();
sw.Start();
int total = 0;
for (int i = 0; i < 100000; i++)
{
var n = Bob.DeepCopy();
total += n.Age;
}
Console.Write("  Elapsed time: {0},{1}\n", sw.Elapsed, total);
}
{
Console.Write("Demo of deep copy, using class and serialize/deserialize:\n");
int total = 0;
var sw = new Stopwatch();
sw.Start();
var Bob = new Person(30, "Lamborghini");
for (int i = 0; i < 100000; i++)
{
var BobsSon = MyDeepCopy.DeepCopy<Person>(Bob);
total += BobsSon.Age;
}
Console.Write("  Elapsed time: {0},{1}\n", sw.Elapsed, total);
}
Console.ReadKey();
}

再次注意,如果你使用用于深度复制的嵌套MemberwiseClone,你必须手动为类中的每个嵌套级别实现一个ShallowCopy,以及一个DeepCopy,调用所有所说的ShallowCopy方法来创建一个完整的克隆。这很简单:总共只有几行,参见上面的演示代码。

注意,在克隆对象时,“struct”和“class”之间有很大的区别:

  • 如果你有一个“struct”,它是一个值类型,所以你可以复制它,内容将被克隆。
  • 如果你有一个“类”,它是一个引用类型,所以如果你复制它,你所做的只是复制指向它的指针。要创建一个真正的克隆,您必须更有创意,并使用在内存中创建原始对象的另一个副本的方法。
  • 克隆对象不正确会导致非常难以确定的错误。在产品代码中,我倾向于实现一个校验和,以再次检查对象是否已被正确克隆,并且没有被对它的另一个引用损坏。这个校验和可以在释放模式下关闭。
  • 我发现这个方法非常有用:通常,你只想克隆对象的一部分,而不是整个对象。对于修改对象,然后将修改后的副本提供给队列的任何用例,这也是必不可少的。

更新

也许可以使用反射来递归遍历对象图来进行深度复制。WCF使用这种技术来序列化一个对象,包括它的所有子对象。诀窍是用一个属性注释所有子对象,使其可被发现。但是,您可能会失去一些性能优势。

更新

独立速度测试的报价(见下面的评论):

我已经用尼尔的序列化/反序列化运行了我自己的速度测试 扩展方法,Contango的嵌套MemberwiseClone, Alex Burtsev的 基于反射的扩展方法和AutoMapper, 100万次 每一个。序列化-反序列化是最慢的,需要15.7秒。然后 AutoMapper出现了,用时10.1秒。更快的是 基于反射的方法,耗时2.4秒。到目前为止最快的是 嵌套MemberwiseClone,耗时0.1秒。归结为性能 而不是向每个类添加代码来克隆它的麻烦。如果性能 亚历克斯·伯采夫的方法不是问题。 - Simon Tewsi

我认为BinaryFormatter方法相对较慢(这让我很惊讶!)。如果某些对象满足ProtoBuf的要求,你可以使用ProtoBuf. net。从ProtoBuf入门页面(http://code.google.com/p/protobuf-net/wiki/GettingStarted):

支持的类型说明:

自定义类:

  • 被标记为数据契约
  • 有一个无参数的构造函数
  • 对于Silverlight:是公共的
  • 许多公共原语等等。
  • 维度数组:T[]
  • List<T> / IList<T>
  • Dictionary<TKey, TValue> / IDictionary<TKey, TValue>
  • 实现IEnumerable<T>并具有Add(T)方法的任何类型

代码假设类型将围绕所选成员进行更改。因此,不支持自定义结构,因为它们应该是不可变的。

如果你的课程符合这些要求,你可以尝试:

public static void deepCopy<T>(ref T object2Copy, ref T objectCopy)
{
using (var stream = new MemoryStream())
{
Serializer.Serialize(stream, object2Copy);
stream.Position = 0;
objectCopy = Serializer.Deserialize<T>(stream);
}
}

这确实非常快……

编辑:

下面是对其进行修改的代码(在. net 4.6上进行了测试)。它使用System.Xml.Serialization和System.IO。不需要将类标记为可序列化的。

public void DeepCopy<T>(ref T object2Copy, ref T objectCopy)
{
using (var stream = new MemoryStream())
{
var serializer = new XS.XmlSerializer(typeof(T));


serializer.Serialize(stream, object2Copy);
stream.Position = 0;
objectCopy = (T)serializer.Deserialize(stream);
}
}

你可以试试这个

    public static object DeepCopy(object obj)
{
if (obj == null)
return null;
Type type = obj.GetType();


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(DeepCopy(array.GetValue(i)), i);
}
return Convert.ChangeType(copied, obj.GetType());
}
else if (type.IsClass)
{


object toret = Activator.CreateInstance(obj.GetType());
FieldInfo[] fields = type.GetFields(BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.Instance);
foreach (FieldInfo field in fields)
{
object fieldValue = field.GetValue(obj);
if (fieldValue == null)
continue;
field.SetValue(toret, DeepCopy(fieldValue));
}
return toret;
}
else
throw new ArgumentException("Unknown type");
}

感谢代码项目上的解毒83 文章

最好的方法是:

    public interface IDeepClonable<T> where T : class
{
T DeepClone();
}


public class MyObj : IDeepClonable<MyObj>
{
public MyObj Clone()
{
var myObj = new MyObj();
myObj._field1 = _field1;//value type
myObj._field2 = _field2;//value type
myObj._field3 = _field3;//value type


if (_child != null)
{
myObj._child = _child.DeepClone(); //reference type .DeepClone() that does the same
}


int len = _array.Length;
myObj._array = new MyObj[len]; // array / collection
for (int i = 0; i < len; i++)
{
myObj._array[i] = _array[i];
}


return myObj;
}


private bool _field1;
public bool Field1
{
get { return _field1; }
set { _field1 = value; }
}


private int _field2;
public int Property2
{
get { return _field2; }
set { _field2 = value; }
}


private string _field3;
public string Property3
{
get { return _field3; }
set { _field3 = value; }
}


private MyObj _child;
private MyObj Child
{
get { return _child; }
set { _child = value; }
}


private MyObj[] _array = new MyObj[4];
}

I 写了一个深度对象复制扩展的方法,基于递归“MemberwiseClone”。它是快速的(快三倍比BinaryFormatter),它适用于任何对象。您不需要默认构造函数或可序列化属性。

源代码:

using System.Collections.Generic;
using System.Reflection;
using System.ArrayExtensions;


namespace System
{
public static class ObjectExtensions
{
private static readonly MethodInfo CloneMethod = typeof(Object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance);


public static bool IsPrimitive(this Type type)
{
if (type == typeof(String)) return true;
return (type.IsValueType & type.IsPrimitive);
}


public static Object Copy(this Object originalObject)
{
return InternalCopy(originalObject, new Dictionary<Object, Object>(new ReferenceEqualityComparer()));
}
private static Object InternalCopy(Object originalObject, IDictionary<Object, Object> visited)
{
if (originalObject == null) return null;
var typeToReflect = originalObject.GetType();
if (IsPrimitive(typeToReflect)) return originalObject;
if (visited.ContainsKey(originalObject)) return visited[originalObject];
if (typeof(Delegate).IsAssignableFrom(typeToReflect)) return null;
var cloneObject = CloneMethod.Invoke(originalObject, null);
if (typeToReflect.IsArray)
{
var arrayType = typeToReflect.GetElementType();
if (IsPrimitive(arrayType) == false)
{
Array clonedArray = (Array)cloneObject;
clonedArray.ForEach((array, indices) => array.SetValue(InternalCopy(clonedArray.GetValue(indices), visited), indices));
}


}
visited.Add(originalObject, cloneObject);
CopyFields(originalObject, visited, cloneObject, typeToReflect);
RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect);
return cloneObject;
}


private static void RecursiveCopyBaseTypePrivateFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect)
{
if (typeToReflect.BaseType != null)
{
RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect.BaseType);
CopyFields(originalObject, visited, cloneObject, typeToReflect.BaseType, BindingFlags.Instance | BindingFlags.NonPublic, info => info.IsPrivate);
}
}


private static void CopyFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy, Func<FieldInfo, bool> filter = null)
{
foreach (FieldInfo fieldInfo in typeToReflect.GetFields(bindingFlags))
{
if (filter != null && filter(fieldInfo) == false) continue;
if (IsPrimitive(fieldInfo.FieldType)) continue;
var originalFieldValue = fieldInfo.GetValue(originalObject);
var clonedFieldValue = InternalCopy(originalFieldValue, visited);
fieldInfo.SetValue(cloneObject, clonedFieldValue);
}
}
public static T Copy<T>(this T original)
{
return (T)Copy((Object)original);
}
}


public class ReferenceEqualityComparer : EqualityComparer<Object>
{
public override bool Equals(object x, object y)
{
return ReferenceEquals(x, y);
}
public override int GetHashCode(object obj)
{
if (obj == null) return 0;
return obj.GetHashCode();
}
}


namespace ArrayExtensions
{
public static class ArrayExtensions
{
public static void ForEach(this Array array, Action<Array, int[]> action)
{
if (array.LongLength == 0) return;
ArrayTraverse walker = new ArrayTraverse(array);
do action(array, walker.Position);
while (walker.Step());
}
}


internal class ArrayTraverse
{
public int[] Position;
private int[] maxLengths;


public ArrayTraverse(Array array)
{
maxLengths = new int[array.Rank];
for (int i = 0; i < array.Rank; ++i)
{
maxLengths[i] = array.GetLength(i) - 1;
}
Position = new int[array.Rank];
}


public bool Step()
{
for (int i = 0; i < Position.Length; ++i)
{
if (Position[i] < maxLengths[i])
{
Position[i]++;
for (int j = 0; j < i; j++)
{
Position[j] = 0;
}
return true;
}
}
return false;
}
}
}


}

MSDN文档似乎暗示Clone应该执行深度复制,但它从未明确说明:

ICloneable接口包含一个成员,克隆,它的目的是支持克隆超出MemberWiseClone提供的克隆…

你会发现我的帖子很有帮助。

http://pragmaticcoding.com/index.php/cloning-objects-in-c/