C# – 深度克隆对象

我想做的事情如下:

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

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

我不经常需要这个功能,所以当有必要的时候,我已经使用了创建一个新对象然后单独复制每个属性,但它总是让我觉得有更好或更优雅的处理方式情况。

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


虽然标准做法是实现ICloneable界面(这里描述,所以我不会反刍),这是一个很好的深度克隆对象复印机我在前一段时间在代码项目中找到并将其合并到我们的东西中。

正如其他地方所提到的,它确实需要您的对象可序列化。

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

/// <summary>
/// Reference Article http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
/// Provides a method for performing a deep copy of an object.
/// Binary Serialization is used to perform the copy.
/// </summary>
public static class ObjectCopier
{
    /// <summary>
    /// Perform a deep Copy of the object.
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T Clone<T>(T source)
    {
        if (!typeof(T).IsSerializable)
        {
            throw new ArgumentException("The type must be serializable.", "source");
        }

        // Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        IFormatter formatter = new BinaryFormatter();
        Stream stream = new MemoryStream();
        using (stream)
        {
            formatter.Serialize(stream, source);
            stream.Seek(0, SeekOrigin.Begin);
            return (T)formatter.Deserialize(stream);
        }
    }
}

这个想法是它序列化你的对象,然后将它反序列化为一个新的对象。好处是,当对象过于复杂时,您不必担心克隆所有内容。

并使用扩展方法(也来自最初引用的源):

如果您更喜欢使用C#3.0 的新扩展方法,请将方法更改为具有以下签名:

public static T Clone<T>(this T source)
{
   //...
}

现在方法调用就变成了objectBeingCloned.Clone();

编辑(2015年1月10日)以为我会重新审视这一点,提到我最近开始使用(Newtonsoft)Json这样做,它应该更轻,并避免[Serializable]标签的开销。(注意 @atconway在评论中指出私有成员不是使用JSON方法克隆的)

/// <summary>
/// Perform a deep Copy of the object, using Json as a serialisation method. NOTE: Private members are not cloned using this method.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneJson<T>(this T source)
{            
    // Don't serialize a null object, simply return the default for that object
    if (Object.ReferenceEquals(source, null))
    {
        return default(T);
    }

    // initialize inner objects individually
    // for example in default constructor some list property initialized with some values,
    // but in 'source' these items are cleaned -
    // without ObjectCreationHandling.Replace default constructor values will be added to result
    var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};

    return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}

我想要一个非常简单的对象,主要是原始和列表的克隆人。如果你的对象是开箱即用的JSON serializable,那么这个方法就可以了。这不需要修改或实现克隆类上的接口,只需要像JSON.NET这样的JSON序列化程序。

public static T Clone<T>(T source)
{
    var serialized = JsonConvert.SerializeObject(source);
    return JsonConvert.DeserializeObject<T>(serialized);
}

不使用的原因ICloneable不是因为它没有一个通用的接口。 不使用它的原因是因为它含糊不清。它不清楚你是否得到浅拷贝或深拷贝; 这取决于实施者。

是的,MemberwiseClone做一个浅的副本,但相反的MemberwiseClone不是Clone; 或许DeepClone,它可能不存在。通过其ICloneable接口使用对象时,您无法知道底层对象执行哪种克隆。(并且XML注释不会说清楚,因为您将获得接口注释而不是对象的Clone方法上的注释。)

我通常做的只是制作一个Copy完全符合我想要的方法。


在阅读了很多关于此处链接的选项以及此问题的可能解决方案后,我相信所有选项都在Ian P的链接中得到了很好的总结(所有其他选项都是这些选项的变体),最佳解决方案由Pedro77关于问题评论的链接

所以我将在这里复制这两个参考文献的相关部分。这样我们可以:

在c sharp中克隆对象最好的办法!

首先,这些都是我们的选择:

文章由表达式树的快速深复制 有也被序列化,反射和表达式树克隆的性能对比。

为什么我选择ICloneable(即手动)

Venkat Subramaniam先生(此处的冗余链接)详细解释了原因

他的所有文章围绕着一个试图适用于大多数情况的例子,使用3个对象:大脑城市。我们想要克隆一个人,它将拥有自己的大脑但是同一个城市。你可以想象上面的任何其他方法可以带来或阅读文章的所有问题。

这是他对他的结论的略微修改版本:

通过指定New后跟类名来复制对象通常会导致代码不可扩展。使用clone,原型模式的应用,是实现这一目标的更好方法。但是,使用C#(和Java)中提供的克隆也很成问题。最好提供受保护(非公共)的复制构造函数,并从克隆方法中调用它。这使我们能够将创建对象的任务委托给类本身的实例,从而提供可扩展性,并使用受保护的拷贝构造函数安全地创建对象。

希望这个实现可以使事情变得清晰:

public class Person : ICloneable
{
    private final Brain brain; // brain is final since I do not want 
                // any transplant on it once created!
    private int age;
    public Person(Brain aBrain, int theAge)
    {
        brain = aBrain; 
        age = theAge;
    }
    protected Person(Person another)
    {
        Brain refBrain = null;
        try
        {
            refBrain = (Brain) another.brain.clone();
            // You can set the brain in the constructor
        }
        catch(CloneNotSupportedException e) {}
        brain = refBrain;
        age = another.age;
    }
    public String toString()
    {
        return "This is person with " + brain;
        // Not meant to sound rude as it reads!
    }
    public Object clone()
    {
        return new Person(this);
    }
    
}

现在考虑从Person派生一个类。

public class SkilledPerson extends Person
{
    private String theSkills;
    public SkilledPerson(Brain aBrain, int theAge, String skills)
    {
        super(aBrain, theAge);
        theSkills = skills;
    }
    protected SkilledPerson(SkilledPerson another)
    {
        super(another);
        theSkills = another.theSkills;
    }

    public Object clone()
    {
        return new SkilledPerson(this);
    }
    public String toString()
    {
        return "SkilledPerson: " + super.toString();
    }
}

您可以尝试运行以下代码:

public class User
{
    public static void play(Person p)
    {
        Person another = (Person) p.clone();
        System.out.println(p);
        System.out.println(another);
    }
    public static void main(String[] args)
    {
        Person sam = new Person(new Brain(), 1);
        play(sam);
        SkilledPerson bob = new SkilledPerson(new SmarterBrain(), 1, "Writer");
        play(bob);
    }
}

产生的输出将是:

This is person with Brain@1fcc69
This is person with Brain@253498
SkilledPerson: This is person with SmarterBrain@1fef6f
SkilledPerson: This is person with SmarterBrain@209f4e

注意,如果我们保持对象数量的计数,这里实现的克隆将保持对象数量的正确计数。

Tags:, ,

添加评论

友情链接:蝴蝶教程