在C#中创建深层复制

时间:2010-09-05 17:42:03

标签: c# deep-copy

我想制作一个对象的深层副本,以便我可以更改新副本,并且仍然可以选择取消我的更改并取回原始对象。

我的问题是这个对象可以是任何类型,甚至来自未知的程序集。 我无法使用BinaryFormatterXmlSerializer,因为该对象不必具有[Serializable]属性。

我尝试使用Object.MemberwiseClone()方法执行此操作:

public object DeepCopy(object obj)
{
    var memberwiseClone = typeof(object).GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic);

    var newCopy = memberwiseClone.Invoke(obj, new object[0]);

    foreach (var field in newCopy.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
    {
        if (!field.FieldType.IsPrimitive && field.FieldType != typeof(string))
        {
            var fieldCopy = DeepCopy(field.GetValue(newCopy));
            field.SetValue(newCopy, fieldCopy);
        }
    }
    return newCopy;
}

这个问题是它没有处理可枚举(数组,列表等),而不是字典。

那么,如何在C#中制作未知对象的深层副本?

TNX很多!

9 个答案:

答案 0 :(得分:13)

完全不可能对任意对象进行深层复制。

例如,您将如何处理ControlFileStreamHttpWebResponse

您的代码无法知道对象的工作方式以及字段应包含的内容。

不要这样做 这是灾难的秘诀。

答案 1 :(得分:4)

制作任意对象的深层副本非常困难。如果对象包含对具有写入功能的打开文件或网络连接等资源的访问权限,该怎么办?在不知道对象持有什么类型的信息的情况下,我很难制作一个对象的副本,并使其功能完全相同。您可以使用反射来执行此操作,但这会非常困难。对于初学者,你必须有一些列表来保存你复制的所有对象,否则,如果A引用B和B引用A,那么你可能会以无限循环结束。

答案 2 :(得分:4)

同意SLaks。你总是需要自定义复制语义,只需要在你希望拥有深拷贝或平面拷贝的天气之间进行distingush。 (什么是参考文献,复合参考文献中包含的参考文献。)

您所谈论的模式是memento pattern

阅读有关如何实施它的文章。但基本上它结果是创建自定义复制facitlity。无论是课堂内部还是外部的“复制工厂”。

答案 3 :(得分:3)

要回答您的问题,您可以通过调用Array.CreateInstance深层复制数组,并通过调用GetValueSetValue来复制数组的内容。

但是,您不应该为任意对象执行此操作。

例如:

  • 事件处理程序怎么样?
  • 某些对象具有唯一ID;您将最终创建重复的ID
  • 引用父母的物品怎么样?

答案 4 :(得分:2)

正如其他人所说,深度复制任意对象可能是灾难性的。但是,如果您非常确定要尝试克隆的对象,则仍可以尝试此操作。

关于原始方法的两件事:

  • 如果是循环引用,您将陷入无限循环;
  • 您唯一需要担心的特殊情况是复制数组成员。其他所有内容(列表,哈希集,字典等)最终都会归结为(或者树木和链接列表将以标准方式进行深度复制)。

另请注意,有一种方法可以创建任意类对象而无需调用任何构造函数。 BinaryFormatter使用它并且它是公开的。不幸的是,我不记得它叫什么,它住在哪里。关于运行时或编组的事情。

更新: System.Runtime.Serialization.FormatterServices.GetUninitializedObject请注意

  

您不能使用GetUninitializedObject方法创建从ContextBoundObject类派生的类型实例。

答案 5 :(得分:2)

好的,我稍微修改了你的例行程序。你需要清理它,但它应该完成你想要的。我没有针对控件或文件流对此进行测试,应该注意这些实例。

我避免使用Activator.CreateInstance的成员克隆。这将创建引用类型和复制值类型的新实例。如果您使用具有多维数组的对象,则需要使用数组排名并对每个排名进行迭代。

    static object DeepCopyMine(object obj)
    {
        if (obj == null) return null;

        object newCopy;
        if (obj.GetType().IsArray)
        {
            var t = obj.GetType();
            var e = t.GetElementType();
            var r = t.GetArrayRank();
            Array a = (Array)obj;
            newCopy = Array.CreateInstance(e, a.Length);
            Array n = (Array)newCopy;
            for (int i=0; i<a.Length; i++)
            {
                n.SetValue(DeepCopyMine(a.GetValue(i)), i);
            }
            return newCopy;
        }
        else
        {
            newCopy = Activator.CreateInstance(obj.GetType(), true);
        }

        foreach (var field in newCopy.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
        {
            if (!field.FieldType.IsPrimitive && field.FieldType != typeof(string))
            {
                var fieldCopy = DeepCopyMine(field.GetValue(obj));
                field.SetValue(newCopy, fieldCopy);
            }
            else
            {
                field.SetValue(newCopy, field.GetValue(obj));
            }
        }
        return newCopy;
    }

答案 6 :(得分:1)

不复制任意对象的另一个原因:如果不检查对象后面的代码,该对象将如何与系统中的其他对象相关联,或某些值是否神奇,则无法知道。例如,两个对象都可以保存对包含一个等于五的整数的数组的引用。这两个数组都在程序的其他地方使用。什么问题可能不是任何一个数组碰巧保持值为5,而是写入数组可能对其他程序执行产生的影响。如果序列化程序的作者不知道数组的用途,那就无法保留这种关系。

答案 7 :(得分:0)

以下是answer by @schoetbi的详细说明。你需要告诉班级如何克隆自己。 C#不区分聚合和组合,并使用对象引用。

如果你有一个用于存储汽车信息的类,它可能有实例字段,如引擎,车轮等(组合),还有制造商(聚合)。两者都存储为参考。

如果你克隆了汽车,你会期望克隆发动机和车轮,但你肯定不希望克隆制造商。

答案 8 :(得分:0)

对于深拷贝,我使用了 Newtonsoft 和 create 和泛型方法,例如:

public T DeepCopy<T>(T objectToCopy){
   var objectSerialized = JsonConvert.SerializeObject(objectToCopy);
   return JsonConvert.DeserializeObject<T>(objectSerialized);
}

我可以用来解决该问题的最佳解决方案。

相关问题