【问题标题】:Object deep clone implementation对象深度克隆实现
【发布时间】:2014-02-06 00:21:18
【问题描述】:

我必须实现通用扩展 deepclone 方法,该方法可以与任何引用类型实例一起使用以获取其深层副本。我将其实现为以下

static class ClassCopy
{
    static public T DeepClone<T> (this T instance)
    {
        if (instance == null) return null;
        var type = instance.GetType();
        T copy;
        var flags = BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.NonPublic |
                    BindingFlags.Instance;

        var fields = type.GetFields(flags);

        // If type is serializable - create instance copy using BinaryFormatter
        if (type.IsSerializable)
        {
            using (var stream = new MemoryStream())
            {
                var formatter = new BinaryFormatter();
                formatter.Serialize(stream, instance);
                stream.Position = 0;
                copy = (T) formatter.Deserialize(stream);
            }

            // Copy all fiels  which are not marked as serializable 
            foreach (var field in fields)
            {
                if (!field.IsNotSerialized) continue;
                var value = field.GetValue(instance);

                //Recursion!!!
                //for each embedded object also create deep copy
                value = value != null  ? value.DeepClone() : value;
                field.SetValue(copy, value);
            }
        }
        else
        {
            // If type is not serializable - create instance copy using Activator
            //(if there is default constructor)
            // or FormatterServices ( if there is no constractor)

            copy = CreateInstance<T>(type);
            foreach (var field in fields)
            {
                var value = field.GetValue(instance);

                //Recursion!!!
                value = value != null  ? value.DeepClone() : value;
                field.SetValue(copy, value);
            }
        }

        //Copy all properties 
        //In order to copy all backing fields  for auto-implemented properties

        var properties = type.GetProperties(flags|BindingFlags.SetProperty);
        foreach (var property in properties)
        {
            if (property.CanWrite)
            {
                var value = property.GetValue(instance);

                //Recursion!!!
                value = value != null ? value.DeepClone() : null;
                property.SetValue(copy, value);
            }
        }
        return copy;
    }

    private static T CreateInstance<T>(Type t) where T: class
    {
        T instance;
        var constructor = t.GetConstructor(Type.EmptyTypes);
        if (constructor != null)
        {
            instance = Activator.CreateInstance(t) as T;
            return instance;
        }
        instance = FormatterServices.GetUninitializedObject(t) as T;
        return instance;
    }
}

效果很好。但是,如果要克隆的对象及其引用类型字段具有相互引用,则此代码会导致无限循环。 例如

private static void Main(string[] args)
{
    var parent = new Parent();
    parent.Child = new Child();
    parent.Child.Parent = parent;
    //Infinite Loop!!!
    var parent1 = parent.DeepClone();
}

class Parent
{
    public Child Child { get; set; }
}
class Child
{
    public Parent Parent { get; set; }
}

有谁知道如何实现这个任务?它应该按字面意思实施,不允许有任何变化(这是一个实习)。非常感谢任何提示!

【问题讨论】:

  • 看不到通用深度克隆方法是有原因的。为了正确完成它们实际上需要根据所讨论的特定类型来实现。鉴于您应该非常很少需要实际使用此类实现,这通常不是问题。您需要这样的东西实际上表明可能有问题。
  • 我明白了。但这是一个实习,所以我必须以某种方式去做。我不知道,也许为了解决这个问题,我应该为已经克隆的对象添加一些存储空间,以免再次克隆它们。但这会为所有对象链提供深层副本吗?我在这里真的很绝望
  • 您必须保留每个克隆对象的字典(通过引用或hash 键入)及其克隆的引用。在克隆新对象之前,请检查它是否已被克隆。

标签: c# recursion extension-methods deep-copy


【解决方案1】:

深度克隆对象的一个​​老技巧是序列化和反序列化它们,从而创建新实例。

public T deepClone<T>(T toClone) where T : class
{
    string tmp = JsonConvert.SerializeObject(toClone);
    return JsonConvert.DeserializeObject<T>(tmp);            
}

我与Newtonsoft.Json 进行了广泛的合作,它为您的问题提供了内置解决方案。默认情况下,它会检测对象是否已经被序列化并抛出异常。但是,您可以将其配置为序列化对对象的引用以绕过循环引用。它不是内联序列化对象,而是序列化对该对象的引用,并保证对对象的每个引用只序列化一次。

此外,默认情况下仅序列化公共字段/属性。还有一个用于序列化私有字段的附加设置。

public T deepClone<T>(T toClone) where T : class
{
    JsonSerializerSettings settings = new JsonSerializerSettings();
    settings.PreserveReferencesHandling = PreserveReferencesHandling.Objects;

    DefaultContractResolver dcr = new DefaultContractResolver();
    dcr.DefaultMembersSearchFlags |= System.Reflection.BindingFlags.NonPublic;
    settings.ContractResolver = dcr;

    string tmp = JsonConvert.SerializeObject(toClone, settings);
    return JsonConvert.DeserializeObject<T>(tmp);
}

因此,您可以“作弊”并使用这样的代码,也可以复制它的工作原理来实现保留引用的克隆。您给出的父母/孩子的例子只是一种难以克隆的方式。 1对多是另一个。

【讨论】:

  • 序列化不是只对可序列化对象和可序列化成员起作用吗?如您所见,我在示例中使用了二进制序列化。但这并不能解决问题。
  • 好点。通常我与 DTO 合作,它们都是公开的。我已经更新了示例以包含私有字段。
【解决方案2】:

您可以做的是传递映射到其克隆的项目的Dictionary。现在该方法将如下所示:

static private T DeepClone&lt;T&gt; (this T instance, IDictionary&lt;object, object&gt; originalToAlreadyCloned) where T: class

现在您在if (instance == null) return null; 之后要做的第一件事是检查instance 是否存在于originalToAlreadyCloned 中,如果存在,则返回它。

为了填充它,在

之后
  1. copy = (T) formatter.Deserialize(stream);
  2. copy = CreateInstance&lt;T&gt;(type);

致电originalToAlreadyCloned.Add(instance, copy);

最后,提供一个新的顶层方法:

static private T DeepClone&lt;T&gt; (this T instance) where T: class 简单地调用DeepClone(instance, new Dictionary&lt;object, object&gt;());

顺便说一句,value = value != null &amp;&amp; value.GetType().IsClass? value.DeepClone() : null; 似乎是错误的。您的意思是,如果value 不是一个类,请将其设置为空。但是如果不是类,一般不能设置为null。我不知道你为什么不只克隆这些项目,以及为什么 DeepClone 仅限于类。

【讨论】:

  • 谢谢,试试这个。感谢您注意到错误 - 它们是凌晨 3 点编码的结果。
猜你喜欢
  • 2017-09-01
  • 2010-09-09
  • 2011-07-26
  • 2012-06-05
  • 2013-09-06
  • 2013-10-11
  • 2018-03-21
相关资源
最近更新 更多