我想做一些事情,比如:

MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();

然后对未反映在原始对象中的新对象进行更改。

我通常不需要这个功能,所以当有必要时,我会创建一个新对象,然后单独复制每个属性,但这总是让我觉得有更好或更优雅的方法来处理这种情况。

如何克隆或深度复制对象,以便在原始对象中不反映任何更改的情况下修改克隆的对象?


当前回答

问:我为什么选择这个答案?

如果您想要.NET所能达到的最快速度,请选择此答案。如果您想要一种非常简单的克隆方法,请忽略这个答案。

换言之,除非您有需要解决的性能瓶颈,并且您可以使用分析器来证明这一点,否则请使用另一个答案。

比其他方法快10倍

执行深度克隆的以下方法是:

比任何涉及序列化/反序列化的速度快10倍;非常接近.NET所能达到的理论最大速度。

方法。。。

为了达到最高速度,可以使用Nested MemberwiseClone进行深度复制。它与复制值结构的速度几乎相同,并且比(a)反射或(b)序列化(如本页其他答案所述)快得多。

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

下面是显示100000个克隆的相对性能差异的代码输出:

嵌套结构上的嵌套MemberwiseClone为1.08秒嵌套类上的嵌套MemberwiseClone为4.77秒39.93秒用于序列化/反序列化

在类上使用Nested MemberwiseColone几乎与复制结构一样快,而且复制结构的速度非常接近.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 MemberwiseColone进行深度复制,则必须为类中的每个嵌套级别手动实现ShallowCopy,以及调用所有所述ShallowCopy方法以创建完整克隆的DeepCopy。这很简单:总共只有几行,请参见上面的演示代码。

值类型与引用类型

请注意,在克隆对象时,“结构”和“类”之间有很大的区别:

如果您有一个“struct”,它是一个值类型,因此您可以复制它,内容将被克隆(但除非您使用本文中的技术,否则它只能进行浅层克隆)。如果你有一个“类”,它是一个引用类型,所以如果你复制它,你所做的就是复制指向它的指针。要创建一个真正的克隆,你必须更具创造力,利用值类型和引用类型之间的差异,从而在内存中创建原始对象的另一个副本。

请参见值类型和引用类型之间的差异。

帮助调试的校验和

错误地克隆对象会导致很难确定错误。在生产代码中,我倾向于实现一个校验和,以双重检查对象是否已正确克隆,并且没有被另一个引用损坏。此校验和可以在发布模式下关闭。我发现这个方法非常有用:通常,您只想克隆对象的一部分,而不是整个对象。

对于将许多线程与许多其他线程分离非常有用

该代码的一个优秀用例是将嵌套类或结构的克隆送入队列,以实现生产者/消费者模式。

我们可以让一个(或多个)线程修改自己拥有的类,然后将该类的完整副本推送到并发队列中。然后,我们有一个(或多个)线程将这些类的副本拉出并处理它们。

这在实践中非常有效,并允许我们将许多线程(生产者)与一个或多个线程(消费者)分离。

而且这种方法也非常快:如果我们使用嵌套结构,它比序列化/反序列化嵌套类快35倍,并且允许我们利用机器上所有可用的线程。

使现代化

显然,ExpressMapper与上面的手工编码一样快,甚至更快。我可能需要看看它们与探查器的比较。

其他回答

我知道,这个问题和答案在这里停留了一段时间,下面不是很好的答案,而是观察,最近我在检查是否确实没有克隆隐私(如果没有,我就不会是我自己了;)时,我很高兴地复制了粘贴的@johnc更新的答案。

我简单地制作了自己的扩展方法(这几乎是上述答案的复制粘贴):

public static class CloneThroughJsonExtension
{
    private static readonly JsonSerializerSettings DeserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };

    public static T CloneThroughJson<T>(this T source)
    {
        return ReferenceEquals(source, null) ? default(T) : JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), DeserializeSettings);
    }
}

并天真地放弃了这样的课程(事实上,有更多这样的课程,但它们是无关的):

public class WhatTheHeck
{
    public string PrivateSet { get; private set; } // matches ctor param name

    public string GetOnly { get; } // matches ctor param name

    private readonly string _indirectField;
    public string Indirect => $"Inception of: {_indirectField} "; // matches ctor param name
    public string RealIndirectFieldVaule => _indirectField;

    public WhatTheHeck(string privateSet, string getOnly, string indirect)
    {
        PrivateSet = privateSet;
        GetOnly = getOnly;
        _indirectField = indirect;
    }
}

代码如下:

var clone = new WhatTheHeck("Private-Set-Prop cloned!", "Get-Only-Prop cloned!", "Indirect-Field clonned!").CloneThroughJson();
Console.WriteLine($"1. {clone.PrivateSet}");
Console.WriteLine($"2. {clone.GetOnly}");
Console.WriteLine($"3.1. {clone.Indirect}");
Console.WriteLine($"3.2. {clone.RealIndirectFieldVaule}");

结果是:

1. Private-Set-Prop cloned!
2. Get-Only-Prop cloned!
3.1. Inception of: Inception of: Indirect-Field cloned!
3.2. Inception of: Indirect-Field cloned!

我整个人都在想:什么……所以我抓起Newtonsoft.Json Github repo,开始挖掘。结果是:当反序列化一个恰好只有一个ctor且其参数名匹配(不区分大小写)公共属性名的类型时,它们将作为这些参数传递给ctor。在这里和这里的代码中可以找到一些线索。

要旨

我知道这不是很常见的情况,示例代码有点滥用,但嘿!当我检查灌木丛中是否有龙在等着跳出来咬我的屁股时,我大吃一惊;)

问:我为什么选择这个答案?

如果您想要.NET所能达到的最快速度,请选择此答案。如果您想要一种非常简单的克隆方法,请忽略这个答案。

换言之,除非您有需要解决的性能瓶颈,并且您可以使用分析器来证明这一点,否则请使用另一个答案。

比其他方法快10倍

执行深度克隆的以下方法是:

比任何涉及序列化/反序列化的速度快10倍;非常接近.NET所能达到的理论最大速度。

方法。。。

为了达到最高速度,可以使用Nested MemberwiseClone进行深度复制。它与复制值结构的速度几乎相同,并且比(a)反射或(b)序列化(如本页其他答案所述)快得多。

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

下面是显示100000个克隆的相对性能差异的代码输出:

嵌套结构上的嵌套MemberwiseClone为1.08秒嵌套类上的嵌套MemberwiseClone为4.77秒39.93秒用于序列化/反序列化

在类上使用Nested MemberwiseColone几乎与复制结构一样快,而且复制结构的速度非常接近.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 MemberwiseColone进行深度复制,则必须为类中的每个嵌套级别手动实现ShallowCopy,以及调用所有所述ShallowCopy方法以创建完整克隆的DeepCopy。这很简单:总共只有几行,请参见上面的演示代码。

值类型与引用类型

请注意,在克隆对象时,“结构”和“类”之间有很大的区别:

如果您有一个“struct”,它是一个值类型,因此您可以复制它,内容将被克隆(但除非您使用本文中的技术,否则它只能进行浅层克隆)。如果你有一个“类”,它是一个引用类型,所以如果你复制它,你所做的就是复制指向它的指针。要创建一个真正的克隆,你必须更具创造力,利用值类型和引用类型之间的差异,从而在内存中创建原始对象的另一个副本。

请参见值类型和引用类型之间的差异。

帮助调试的校验和

错误地克隆对象会导致很难确定错误。在生产代码中,我倾向于实现一个校验和,以双重检查对象是否已正确克隆,并且没有被另一个引用损坏。此校验和可以在发布模式下关闭。我发现这个方法非常有用:通常,您只想克隆对象的一部分,而不是整个对象。

对于将许多线程与许多其他线程分离非常有用

该代码的一个优秀用例是将嵌套类或结构的克隆送入队列,以实现生产者/消费者模式。

我们可以让一个(或多个)线程修改自己拥有的类,然后将该类的完整副本推送到并发队列中。然后,我们有一个(或多个)线程将这些类的副本拉出并处理它们。

这在实践中非常有效,并允许我们将许多线程(生产者)与一个或多个线程(消费者)分离。

而且这种方法也非常快:如果我们使用嵌套结构,它比序列化/反序列化嵌套类快35倍,并且允许我们利用机器上所有可用的线程。

使现代化

显然,ExpressMapper与上面的手工编码一样快,甚至更快。我可能需要看看它们与探查器的比较。

我提出这一点是为了克服.NET的一个缺点,即必须手动深度复制List<T>。

我使用这个:

static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements)
{
    foreach (SpotPlacement sp in spotPlacements)
    {
        yield return (SpotPlacement)sp.Clone();
    }
}

在另一个地方:

public object Clone()
{
    OrderItem newOrderItem = new OrderItem();
    ...
    newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
    ...
    return newOrderItem;
}

我试图提出一个这样做的oneliner,但这是不可能的,因为yield不能在匿名方法块中工作。

更好的是,使用通用的List<T>克隆器:

class Utility<T> where T : ICloneable
{
    static public IEnumerable<T> CloneList(List<T> tl)
    {
        foreach (T t in tl)
        {
            yield return (T)t.Clone();
        }
    }
}

C#扩展也将支持“not ISerializable”类型。

 public static class AppExtensions
 {                                                                      
       public static T DeepClone<T>(this T a)
       {
           using (var stream = new MemoryStream())
           {
               var serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));

               serializer.Serialize(stream, a);
               stream.Position = 0;
               return (T)serializer.Deserialize(stream);
           }
       }                                                                    
 }

用法

       var obj2 = obj1.DeepClone()

我也通过反思看到了它的实现。基本上,有一种方法可以遍历对象的成员,并将它们适当地复制到新对象中。当它到达引用类型或集合时,我认为它对自己进行了递归调用。反射是昂贵的,但效果很好。