我想要一个真正的深拷贝.在Java中,这很容易,但是你如何在C#中做到这一点?
我已经看到了一些不同的方法,但我使用通用的实用方法:
public static T DeepClone(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;
我写了一个深度对象复制扩展方法,基于递归"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
以Kilhoffer的解决方案为基础......
使用C#3.0,您可以创建一个扩展方法,如下所示:
public static class ExtensionMethods { // Deep clone public static T DeepClone(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)反射或(b)序列化(如本页其他答案中所述)快一个数量级.
请注意,如果您使用嵌套的MemberwiseClone进行深层复制,则必须为类中的每个嵌套级别手动实现ShallowCopy,并使用DeepCopy调用所有所述的ShallowCopy方法来创建完整的克隆.这很简单:总共只有几行,请参阅下面的演示代码.
以下是显示相对性能差异的代码输出(深度嵌套的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 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调用demo:
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(Bob); total += BobsSon.Age; } Console.Write(" Elapsed time: {0},{1}\n", sw.Elapsed, total); } Console.ReadKey(); }
同样,请注意,如果您使用嵌套的MemberwiseClone进行深层复制,则必须为类中的每个嵌套级别手动实现ShallowCopy,并使用DeepCopy调用所有所述的ShallowCopy方法来创建完整的克隆.这很简单:总共只有几行,请参阅上面的演示代码.
请注意,在克隆对象时,"struct"和"class"之间存在很大差异:
如果你有一个"struct",它是一个值类型,所以你可以复制它,然后克隆内容.
如果你有一个"类",它是一个引用类型,所以如果你复制它,你所做的只是将指针复制到它.要创建真正的克隆,您必须更具创造性,并使用在内存中创建原始对象的另一个副本的方法.
错误地克隆对象可能导致非常难以确定的错误.在生产代码中,我倾向于实现校验和以仔细检查对象是否已正确克隆,并且没有被另一个对它的引用破坏.可以在释放模式下关闭此校验和.
我发现这个方法非常有用:通常,你只想克隆对象的一部分,而不是整个事物.对于修改对象,然后将修改后的副本送入队列的任何用例,它也是必不可少的.
更新
可能使用反射以递归方式遍历对象图以进行深层复制.WCF使用此技术序列化对象,包括其所有子对象.诀窍是使用使其可被发现的属性来注释所有子对象.但是,您可能会失去一些性能优势.
更新
引用独立速度测试(见下面的评论):
我使用Neil的序列化/反序列化扩展方法,Contango的嵌套MemberwiseClone,Alex Burtsev的基于反射的扩展方法和AutoMapper,每个100万次运行我自己的速度测试.Serialize-deserialize最慢,耗时15.7秒.然后是AutoMapper,耗时10.1秒.基于反射的方法要快2.4秒,速度要快得多.到目前为止,最快的是嵌套的MemberwiseClone,耗时0.1秒.归结为性能与为每个类添加代码以克隆它的麻烦.如果表现不是问题,请选择Alex Burtsev的方法. - Simon Tewsi
我相信BinaryFormatter方法相对较慢(这让我感到惊讶!).如果符合ProtoBuf的要求,您可以将ProtoBuf .NET用于某些对象.从ProtoBuf入门页面(http://code.google.com/p/protobuf-net/wiki/GettingStarted):
支持的类型说明:
自定义类:
被标记为数据合同
有一个无参数的构造函数
对于Silverlight:是公开的
许多常见的原语等
单维数组:T []
列出
字典
任何实现IEnumerable
该代码假定类型将在当选成员周围变化.因此,不支持自定义结构,因为它们应该是不可变的.
如果您的班级符合这些要求,您可以尝试:
public static void deepCopy(ref T object2Copy, ref T objectCopy) { using (var stream = new MemoryStream()) { Serializer.Serialize(stream, object2Copy); stream.Position = 0; objectCopy = Serializer.Deserialize (stream); } }
这确实非常快......
编辑:
这是修改此代码的工作代码(在.NET 4.6上测试).它使用System.Xml.Serialization和System.IO.无需将类标记为可序列化.
public void DeepCopy(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"); }
感谢DetoX83 关于代码项目的文章.