【问题标题】:Create a Deep Copy in C#在 C# 中创建深拷贝
【发布时间】:2011-04-08 12:05:53
【问题描述】:

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

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

【问题讨论】:

  • 在任意对象上调用MemberwiseClone邪恶的
  • 我在数组和字典上试过这个,它们似乎都可以工作。我在开头添加了一个空检查。这似乎是基本对象的通用解决方案。正如 Slaks 提到的,它对于文件流来说不是一个好主意,例如它将相同的句柄复制到文件流。它们都将依赖于流的关闭或搜索。如果您知道它并不总是深度克隆,那么它是一个有趣的解决方案。
  • 我还阅读了一个解决方案,您可以在其中检查 ICloneable 接口。如果实现了 ICloneable,则调用它,否则只需创建一个空实例。
  • 我认为它没有完全理解我想要做什么。我正在尝试创建一个 DEEP 副本,这意味着复制对象在属性等中包含的所有对象层次结构。因此,我显示的方法不适用于类数组,只需尝试更改其中一个类,然后你会发现“复制”数组中的值也发生了变化。我希望数组(或任何对象)完全独立,因此更改源对象中的值不会更改我创建的副本。
  • 我们了解您想要做什么。然而,深度复制任意对象完全不可能

标签: c# deep-copy


【解决方案1】:

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

例如,您将如何处理ControlFileStreamHttpWebResponse

您的代码无法知道对象是如何工作的以及它的字段应该包含什么。

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

【讨论】:

  • 我不明白你的意思。为什么我不能创建控件的深层副本?我要做的只是创建与我完全相同的实例,但是在内存中的一个新“位置”中,因此如果我要更改一个,则第二个将不会受到影响。如果可以的话,我会做的是获取对象“位于”内存中的字节并将它们复制到内存中的新位置,然后创建一个指针,以便我可以将它用作.net 中的任何其他对象。我能做到吗?
  • 关于 FileStream 或 HttpWebResponse,我可以确定该对象不是其中之一。该对象必须是一个简单的对象(“数据对象”或类似的东西)。
  • @Orad:控件涉及本机互操作(它们包含窗口句柄)。如果您尝试对其中一个进行深度复制,您将获得两个包装同一个窗口句柄的控件。
【解决方案2】:

制作任意对象的深层副本非常困难。如果对象包含对资源的访问,例如具有写入功能的打开文件或网络连接,该怎么办?如果不知道对象包含什么类型的信息,我将很难复制一个对象,并让它以完全相同的方式运行。您也许可以使用反射来做到这一点,但这会变得非常困难。对于初学者,您必须有某种列表来保存您复制的所有对象,否则,如果 A 引用 B 和 B 引用 A,那么您可能会陷入无限循环。

【讨论】:

    【解决方案3】:

    同意 SLaks。您总是需要自定义复制语义,以区分您想要深拷贝或平面拷贝的天气。 (什么是引用,什么是复合引用中的包含引用。)

    您所说的模式是memento pattern

    阅读有关如何实现它的文章。但基本上事实证明,它创建了一个自定义复制设施。可以是类内部的,也可以是“复制工厂”的外部。

    【讨论】:

    • 我不知道如何使用任何模式。我不知道我要复制的对象是什么。能举个例子吗?
    • Memento 模式在这里描述得非常好:business-patterns.com/DesignPatterns/Behavioural/Memento.pdf.
    • 我仍然不明白如何在我的情况下使用它。在备忘录模式中,我必须为任何需要保存其状态的类创建一个特定的备忘录;但在这种情况下,我需要保存任何对象的状态(甚至是未引用到我的程序集的程序集中的类)。另外,我只需要保存一个状态,备忘录模式(根据我的理解)主要用于保存对象传递的许多状态。
    • 只是为了说清楚。 memenot 模式不是问题的答案。正如我所说:您需要为您的对象定制一个复制工具。
    【解决方案4】:

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

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

    例如:

    • 事件处理程序呢?
    • 有些对象有唯一的 ID;您最终会创建重复的 ID
    • 引用其父对象的对象呢?

    【讨论】:

      【解决方案5】:

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

      关于原始方法的两点:

      • 如果出现循环引用,您将陷入无限循环;
      • 唯一需要担心的特殊情况是复制数组成员。其他一切(列表、哈希集、字典等)最终都会归结为这一点(或者在树和链表的情况下,标准方式将是可深度复制的)。

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

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

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

      【讨论】:

      【解决方案6】:

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

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

          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;
          }
      

      【讨论】:

      • 谢谢你,错误的开发者 - 几乎是逐字逐句的,然后我偶然发现了最后一块...... grr!......但是谢谢!
      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

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

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

        如果你克隆了汽车,你会期望引擎和车轮也被克隆,但你当然不希望制造商也被克隆。

        【讨论】:

          【解决方案9】:

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

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

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

          【讨论】:

            猜你喜欢
            • 2011-10-13
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-04-26
            • 2012-03-23
            • 1970-01-01
            • 2015-08-23
            相关资源
            最近更新 更多