您可以使用 Nested MemberwiseClone 进行深层复制。它与复制值结构的速度几乎相同,并且比 (a) 反射或 (b) 序列化快一个数量级(如本页其他答案中所述)。
请注意,如果您使用 Nested MemberwiseClone 进行深层复制,您必须为类中的每个嵌套级别手动实现 ShallowCopy,以及调用所有的 DeepCopy表示创建完整克隆的 ShallowCopy 方法。这很简单:总共只有几行,见下面的演示代码。
这是显示相对性能差异的代码输出(深度嵌套的 MemberwiseCopy 为 4.77 秒,序列化为 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();
}
再次注意,如果您使用 Nested MemberwiseClone 进行深层复制,您必须为类中的每个嵌套级别手动实现一个 ShallowCopy,以及一个 DeepCopy调用所有所说的 ShallowCopy 方法来创建一个完整的克隆。这很简单:总共只有几行代码,见上面的演示代码。
请注意,在克隆对象时,“结构”和“类”之间存在很大差异:
- 如果你有一个“结构”,它是一个值类型,所以你可以复制它,内容将被克隆。
- 如果你有一个“类”,它是一个引用类型,所以如果你复制它,你所做的就是复制指向它的指针。要创建真正的克隆,您必须更有创意,并使用一种方法在内存中创建原始对象的另一个副本。
- 错误地克隆对象会导致非常难以确定的错误。在生产代码中,我倾向于实施校验和来仔细检查对象是否已正确克隆,并且没有被另一个引用破坏。可以在发布模式下关闭此校验和。
- 我发现这种方法非常有用:通常,您只想克隆对象的一部分,而不是整个对象。对于修改对象,然后将修改后的副本送入队列的任何用例,这也是必不可少的。
更新
可能使用反射来递归遍历对象图以进行深度复制。 WCF 使用这种技术来序列化一个对象,包括它的所有子对象。诀窍是使用使其可发现的属性注释所有子对象。但是,您可能会失去一些性能优势。
更新
引用独立速度测试(见下面的 cmets):
我已经使用 Neil 的序列化/反序列化运行了我自己的速度测试
扩展方法,Contango 的 Nested MemberwiseClone,Alex Burtsev 的
基于反射的扩展方法和AutoMapper,100万次
每个。序列化-反序列化最慢,耗时 15.7 秒。然后
AutoMapper 来了,耗时 10.1 秒。更快的是
基于反射的方法,耗时 2.4 秒。到目前为止最快的是
嵌套的 MemberwiseClone,耗时 0.1 秒。归结为性能
与向每个类添加代码以克隆它的麻烦相比。如果性能
Alex Burtsev 的方法不是问题。
——西蒙·图西