你如何做一个.NET对象的深层副本(特别是C#)?
我想要一个真正的深层复制。 在Java中,这很容易,但是你怎么在C#中做到这一点?
我已经看到了一些不同的方法,但是我使用了一个通用的实用方法:
public static T DeepClone<T>(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;
我写了一个深入的对象副本扩展方法 ,基于recursion的“MemberwiseClone” 。 它速度快(比BinaryFormatter 快三倍),并且适用于任何对象。 您不需要默认的构造函数或可序列化的属性。
基于Kilhoffer的解决scheme…
使用C#3.0,您可以创build一个扩展方法,如下所示:
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();
您可以使用嵌套的MemberwiseClone来执行深层复制 。 它的速度几乎与复制一个值结构的速度相同,并且比(a)reflection或(b)序列化更快(如本页其他答案中所述)。
请注意, 如果您使用嵌套的MemberwiseClone进行深层副本 ,则必须为类中的每个嵌套级别手动实现ShallowCopy,以及调用所有ShallowCopy方法来创build完整克隆的DeepCopy。 这很简单:总共只有几行,请参阅下面的演示代码。
下面是代码的输出,显示了相对的性能差异(深嵌套的MemberwiseCopy为4.77秒,序列化为39.93秒)。 使用嵌套的MemberwiseCopy几乎和复制一个结构一样快,而且复制一个结构相当接近.NET所能达到的理论最大速度。
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,以及调用所有ShallowCopy方法来创build完整克隆的DeepCopy。 这很简单:总共只有几行,请参阅上面的演示代码。
请注意,当涉及克隆一个对象时,“struct”和“class”之间有很大的区别:
- 如果你有一个“结构”,它是一个值types,所以你可以复制它,内容将被克隆。
- 如果你有一个“类”,它是一个引用types,所以如果你复制它,所有你正在做的是复制指针。 要创build一个真正的克隆,您必须更具创造性,并使用一种方法在内存中创build另一个原始对象的副本。
- 不正确地克隆对象可能会导致非常难以locking的错误。 在生产代码中,我倾向于执行校验和来仔细检查对象是否被正确克隆,并且没有被其他引用所破坏。 该校验和可以在释放模式下closures。
- 我觉得这个方法非常有用:通常,你只想克隆对象的一部分,而不是整个事物。 对于修改对象,然后将修改后的副本送入队列的任何用例,这也是非常重要的。
更新
可能使用reflectionrecursion地遍历对象图来进行深度复制。 WCF使用这种技术来序列化一个对象,包括它的所有子对象。 诀窍是注释所有的子对象的属性,使其可发现。 但是,您可能会失去一些性能优势。
更新
引用独立的速度testing(见下面的评论):
我使用Neil的序列化/反序列化扩展方法,Contango的嵌套MemberwiseClone,Alex Burtsev基于reflection的扩展方法和AutoMapper,分别运行了100万次。 序列化 – 反序列化最慢,耗时15.7秒。 然后来到AutoMapper,耗时10.1秒。 基于reflection的方法耗时2.4秒,速度要快得多。 迄今最快的是嵌套MemberwiseClone,花费0.1秒。 归结为性能与为每个类添加代码克隆它的麻烦。 如果performance不是问题,那么请跟随Alex Burtsev的方法。 – Simon Tewsi
我相信BinaryFormatter方法相对较慢(这对我来说是一个惊喜!)。 如果符合ProtoBuf的要求,您可以使用ProtoBuf .NET来处理某些对象。 从ProtoBuf入门页面( http://code.google.com/p/protobuf-net/wiki/GettingStarted ):
关于支持types的说明:
自定义类:
- 被标记为数据合同
- 有一个无参数的构造函数
- 对于Silverlight:是公开的
- 许多常见的基元等
- 单维数组:T []
- 列表<T> / IList <T>
- Dictionary <TKey,TValue> / IDictionary <TKey,TValue>
- 任何实现IEnumerable <T>的types,并具有Add(T)方法
该代码假定types将在当选成员周围是可变的。 因此,自定义结构不被支持,因为它们应该是不可变的。
如果你的课程符合这些要求,你可以尝试:
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); } }
这确实是非常快…
也许你只需要一个浅拷贝,在这种情况下使用Object.MemberWiseClone()
。
MemberWiseClone()
的文档中提供了很好的build议来实现深度复制的策略:
http://msdn.microsoft.com/en-us/library/system.object.memberwiseclone.aspx
你可以试试这个
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"); }
感谢代码项目的DetoX83 文章 。
最好的方法是:
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]; }
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]
属性。
MSDN文档似乎暗示Clone应该执行深层复制,但从未明确说明:
ICloneable接口包含一个成员Clone,它旨在支持MemberWiseClone提供的克隆… MemberwiseClone方法创build一个浅拷贝…
你可以find我的post有帮助。
我有一个更简单的想法。 用一个新的select使用LINQ。
public class Fruit { public string Name {get; set;} public int SeedCount {get; set;} } void SomeMethod() { List<Fruit> originalFruits = new List<Fruit>(); originalFruits.Add(new Fruit {Name="Apple", SeedCount=10}); originalFruits.Add(new Fruit {Name="Banana", SeedCount=0}); //Deep Copy List<Fruit> deepCopiedFruits = from f in originalFruits select new Fruit {Name=f.Name, SeedCount=f.SeedCount}; }