问:为什么我要选择这个答案?
- 如果您希望.NET具有最快的速度,请选择此答案。
- 如果您想要一种非常非常简单的克隆方法,请忽略此答案。
换句话说,除非您有需要解决的性能瓶颈,并且可以通过探查器证明它,否则请选择另一个答案。。
比其他方法快10倍
执行深度克隆的以下方法是:
- 比任何涉及序列化/反序列化的速度快10倍;
- NET能够达到的理论最高速度还算不错。
和方法...
为了获得最高速度,您可以使用Nested MemberwiseClone进行深度复制。它的速度几乎与复制值结构相同,并且比(a)反射或(b)序列化要快得多(如本页其他答案所述)。
请注意,如果您将Nested MemberwiseClone用于深层副本,则必须为类中的每个嵌套级别手动实现ShallowCopy,并必须通过DeepCopy调用所有上述ShallowCopy方法来创建完整的副本。这很简单:总共只有几行,请参见下面的演示代码。
以下是代码输出,显示了100,000个克隆的相对性能差异:
- 嵌套结构上的Nested MemberwiseClone为1.08秒
- 嵌套类上的嵌套MemberwiseClone为4.77秒
- 39.93秒进行序列化/反序列化
在类上使用Nested MemberwiseClone几乎与复制结构一样快,并且复制结构的速度与.NET所能达到的理论最大速度相当。
Demo 1 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 2 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 3 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 1 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\n", sw.Elapsed, total);
}
{
Console.Write("Demo 2 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\n", sw.Elapsed, total);
}
{
Console.Write("Demo 3 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方法来创建完整的副本。这很简单:总共只有几行,请参见上面的演示代码。
值类型与引用类型
请注意,在克隆对象时,“ struct ”和“ class ” 之间有很大的区别:
- 如果您有一个“ struct ”,它是一个值类型,因此您可以复制它,并且内容将被克隆(但是除非您使用本文中的技巧,否则它只会进行浅表克隆)。
- 如果您有一个“ 类 ”,则它是一个引用类型,因此,如果您复制它,那么您所做的就是复制指向它的指针。要创建真正的克隆,您必须更具创造力,并使用值类型和引用类型之间的差异,这会在内存中创建原始对象的另一个副本。
查看值类型和引用类型之间的差异。
校验和以帮助调试
- 错误地克隆对象会导致非常难以修复的错误。在生产代码中,我倾向于实现校验和以再次检查对象是否已正确克隆,并且未被其他引用损坏。可以在发布模式下关闭此校验和。
- 我发现此方法非常有用:通常,您只想克隆对象的一部分,而不是整个对象。
对于将许多线程与许多其他线程解耦非常有用
此代码的一个很好的用例是将嵌套类或结构的克隆馈入队列,以实现生产者/使用者模式。
- 我们可以让一个(或多个)线程修改其拥有的类,然后将此类的完整副本推送到
ConcurrentQueue
。
- 然后,我们有一个(或多个)线程将这些类的副本拉出并进行处理。
这在实践中非常有效,并且使我们能够将许多线程(生产者)与一个或多个线程(消费者)分离。
而且这种方法也非常快:如果使用嵌套结构,它比对嵌套类进行序列化/反序列化要快35倍,并且使我们能够利用计算机上可用的所有线程。
更新资料
显然,ExpressMapper的速度甚至比上述手工编码还快。我可能必须看一下它们与探查器的比较。