繁体   English   中英

在C#中创建深层复制

[英]Create a Deep Copy in C#

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

我的问题是,对象可以是任何类型,即使是来自未知的程序集。 我不能使用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很多!

深度复制任意对象是完全不可能的。

例如,您将如何处理ControlFileStreamHttpWebResponse

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

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

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

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

你所谈论的模式是纪念模式

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

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

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

例如:

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

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

关于原始方法的两件事:

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

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

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

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

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

我避免了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;
    }

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM