在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

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

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

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

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

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

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

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

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

例如:

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

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

关于原始方法的两件事:

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

还要注意,有一种方法允许在不调用任何构造函数的情况下创建任意类对象。 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 

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

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

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

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