我需要实现具有inheritance的C#深层复制构造函数。 有哪些模式可供选择?

我希望在C#中实现我的类层次结构的深度复制

public Class ParentObj : ICloneable { protected int myA; public virtual Object Clone () { ParentObj newObj = new ParentObj(); newObj.myA = theObj.MyA; return newObj; } } public Class ChildObj : ParentObj { protected int myB; public override Object Clone ( ) { Parent newObj = this.base.Clone(); newObj.myB = theObj.MyB; return newObj; } } 

这将不起作用,因为克隆孩子只有父母是新的。 在我的代码中,一些类具有大的层次结构。

这样做的推荐方法是什么? 克隆每个级别的所有内容而不调用基类似乎是错误的? 这个问题必须有一些巧妙的解决方案,它们是什么?

能否感谢大家的回答。 看到一些方法真的很有趣。 我认为如果有人举一个完整性的反思答案的例子会很好。 +1等待!

典型的方法是使用“复制构造函数”模式a la C ++:

  class Base : ICloneable { int x; protected Base(Base other) { x = other.x; } public virtual object Clone() { return new Base(this); } } class Derived : Base { int y; protected Derived(Derived other) : Base(other) { y = other.y; } public override object Clone() { return new Derived(this); } } 

另一种方法是在Clone的实现中使用Object.MemberwiseClone – 这将确保结果始终是正确的类型,并允许覆盖扩展:

  class Base : ICloneable { List xs; public virtual object Clone() { Base result = this.MemberwiseClone(); // xs points to same List object here, but we want // a new List object with copy of data result.xs = new List(xs); return result; } } class Derived : Base { List ys; public override object Clone() { // Cast is legal, because MemberwiseClone() will use the // actual type of the object to instantiate the copy. Derived result = (Derived)base.Clone(); // ys points to same List object here, but we want // a new List object with copy of data result.ys = new List(ys); return result; } } 

这两种方法都要求层次结构中的所有类都遵循该模式。 使用哪一个是优先考虑的问题。

如果你有任何实现ICloneable随机类而不保证实现(除了遵循ICloneable的文档语义),就没有办法扩展它。

尝试序列化技巧:

 public object Clone(object toClone) { BinaryFormatter bf = new BinaryFormatter(); MemoryStream ms= new MemoryStream(); bf.Serialize(ms, toClone); ms.Flush(); ms.Position = 0; return bf.Deserialize(ms); } 

警告:

这段代码应该非常谨慎使用。 使用风险由您自己承担。 此示例按原样提供,没有任何forms的担保。


还有另一种方法可以在对象图上执行深度克隆。 在考虑使用此示例时,请务必注意以下事项:

缺点:

  1. 除非将这些引用提供给Clone(object,…)方法,否则还将克隆对外部类的任何引用。
  2. 在克隆的对象上不会执行任何构造函数,它们将被完全复制。
  3. 不会执行ISerializable或序列化构造函数。
  4. 无法在特定类型上更改此方法的行为。
  5. 它将克隆所有内容,Stream,AppDomain,Form等等,这些可能会以可怕的方式破坏您的应用程序。
  6. 它可能会破坏,而使用序列化方法更有可能继续工作。
  7. 下面的实现使用递归,如果对象图太深,很容易导致堆栈溢出。

那你为什么要用呢?

优点:

  1. 它对所有实例数据进行完整的深层复制,而不需要对象中的编码。
  2. 它保留重构对象中的所有对象图引用(甚至是圆形)。
  3. 它的执行速度是二进制格式化程序的20倍以上,内存消耗较少。
  4. 它不需要任何东西,没有属性,实现的接口,公共属性,没有。

代码用法:

你只需用一个对象来调用它:

 Class1 copy = Clone(myClass1); 

或者假设您有一个子对象,并且您订阅了它的事件……现在您要克隆该子对象。 通过提供克隆的对象列表,您可以保留对象图的一些部分:

 Class1 copy = Clone(myClass1, this); 

执行:

现在让我们先轻松一点……这里是切入点:

 public static T Clone(T input, params object[] stableReferences) { Dictionary graph = new Dictionary(new ReferenceComparer()); foreach (object o in stableReferences) graph.Add(o, o); return InternalClone(input, graph); } 

现在这很简单,它只是在克隆过程中为对象构建一个字典映射,并用任何不应克隆的对象填充它。 你会注意到提供给字典的比较器是一个ReferenceComparer,让我们来看看它的作用:

 class ReferenceComparer : IEqualityComparer { bool IEqualityComparer.Equals(object x, object y) { return Object.ReferenceEquals(x, y); } int IEqualityComparer.GetHashCode(object obj) { return RuntimeHelpers.GetHashCode(obj); } } 

这很容易,只是一个强制使用System.Object的get哈希和引用相等的比较器……现在是艰苦的工作:

 private static T InternalClone(T input, Dictionary graph) { if (input == null || input is string || input.GetType().IsPrimitive) return input; Type inputType = input.GetType(); object exists; if (graph.TryGetValue(input, out exists)) return (T)exists; if (input is Array) { Array arItems = (Array)((Array)(object)input).Clone(); graph.Add(input, arItems); for (long ix = 0; ix < arItems.LongLength; ix++) arItems.SetValue(InternalClone(arItems.GetValue(ix), graph), ix); return (T)(object)arItems; } else if (input is Delegate) { Delegate original = (Delegate)(object)input; Delegate result = null; foreach (Delegate fn in original.GetInvocationList()) { Delegate fnNew; if (graph.TryGetValue(fn, out exists)) fnNew = (Delegate)exists; else { fnNew = Delegate.CreateDelegate(input.GetType(), InternalClone(original.Target, graph), original.Method, true); graph.Add(fn, fnNew); } result = Delegate.Combine(result, fnNew); } graph.Add(input, result); return (T)(object)result; } else { Object output = FormatterServices.GetUninitializedObject(inputType); if (!inputType.IsValueType) graph.Add(input, output); MemberInfo[] fields = inputType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); object[] values = FormatterServices.GetObjectData(input, fields); for (int i = 0; i < values.Length; i++) values[i] = InternalClone(values[i], graph); FormatterServices.PopulateObjectMembers(output, fields, values); return (T)output; } } 

您将注意到数组和委托副本的特殊情况。 每个都有它们自己的原因,第一个Array没有可以克隆的'成员',所以你必须处理它并依赖浅的Clone()成员然后克隆每个元素。 至于代表,它可以在没有特殊情况下工作; 然而,这将更加安全,因为它不会像RuntimeMethodHandle之类的东西那样重复。 如果您打算在核心运行时(如System.Type)中包含层次结构中的其他内容,我建议您以类似的方式显式处理它们。

最后一种情况,也是最常见的,只是使用BinaryFormatter使用的大致相同的例程。 这些允许我们从原始对象中弹出所有实例字段(公共或私有),克隆它们,并将它们粘贴到空对象中。 这里的好处是GetUninitializedObject返回一个没有运行ctor的新实例,这可能会导致问题并降低性能。

上述是否有效将高度依赖于您的特定对象图和其中的数据。 如果您控制图中的对象并且知道它们没有引用像Thread这样的愚蠢的东西,那么上面的代码应该可以很好地工作。

测试:

这是我写的最初测试这个:

 class Test { public Test(string name, params Test[] children) { Print = (Action)Delegate.Combine( new Action(delegate(StringBuilder sb) { sb.AppendLine(this.Name); }), new Action(delegate(StringBuilder sb) { sb.AppendLine(this.Name); }) ); Name = name; Children = children; } public string Name; public Test[] Children; public Action Print; } static void Main(string[] args) { Dictionary data2, data = new Dictionary(StringComparer.OrdinalIgnoreCase); Test a, b, c; data.Add("a", a = new Test("a", new Test("aa"))); a.Children[0].Children = new Test[] { a }; data.Add("b", b = new Test("b", a)); data.Add("c", c = new Test("c")); data2 = Clone(data); Assert.IsFalse(Object.ReferenceEquals(data, data2)); //basic contents test & comparer Assert.IsTrue(data2.ContainsKey("a")); Assert.IsTrue(data2.ContainsKey("A")); Assert.IsTrue(data2.ContainsKey("B")); //nodes are different between data and data2 Assert.IsFalse(Object.ReferenceEquals(data["a"], data2["a"])); Assert.IsFalse(Object.ReferenceEquals(data["a"].Children[0], data2["a"].Children[0])); Assert.IsFalse(Object.ReferenceEquals(data["B"], data2["B"])); Assert.IsFalse(Object.ReferenceEquals(data["B"].Children[0], data2["B"].Children[0])); Assert.IsFalse(Object.ReferenceEquals(data["B"].Children[0], data2["A"])); //graph intra-references still in tact? Assert.IsTrue(Object.ReferenceEquals(data["B"].Children[0], data["A"])); Assert.IsTrue(Object.ReferenceEquals(data2["B"].Children[0], data2["A"])); Assert.IsTrue(Object.ReferenceEquals(data["A"].Children[0].Children[0], data["A"])); Assert.IsTrue(Object.ReferenceEquals(data2["A"].Children[0].Children[0], data2["A"])); data2["A"].Name = "anew"; StringBuilder sb = new StringBuilder(); data2["A"].Print(sb); Assert.AreEqual("anew\r\nanew\r\n", sb.ToString()); } 

最后的说明:

老实说,当时这是一项有趣的运动。 在数据模型上进行深度克隆通常是一件好事。 今天的现实是,生成的大多数数据模型都会使用生成的深度克隆例程来废弃上面的hackery的有用性。 我强烈建议您生成数据模型,并且能够执行深度克隆而不是使用上面的代码。

最好的方法是序列化您的对象,然后返回反序列化的副本。 除了标记为不可序列化的对象外,它将获取有关对象的所有内容,并使inheritance序列化变得容易。

 [Serializable] public class ParentObj: ICloneable { private int myA; [NonSerialized] private object somethingInternal; public virtual object Clone() { MemoryStream ms = new MemoryStream(); BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(ms, this); object clone = formatter.Deserialize(ms); return clone; } } [Serializable] public class ChildObj: ParentObj { private int myB; // No need to override clone, as it will still serialize the current object, including the new myB field } 

它不是最高效的东西,但也不是替代方案:重选。 此选项的好处是它可以无缝inheritance。

  1. 您可以使用reflection来循环所有变量并复制它们。(慢)如果它对您的软件来说很慢,您可以使用DynamicMethod并生成il。
  2. 序列化对象并再次反序列化。

我不认为你在这里正确实现ICloneable; 它需要一个没有参数的Clone()方法。 我建议的是:

 public class ParentObj : ICloneable { public virtual Object Clone() { var obj = new ParentObj(); CopyObject(this, obj); } protected virtual CopyObject(ParentObj source, ParentObj dest) { dest.myA = source.myA; } } public class ChildObj : ParentObj { public override Object Clone() { var obj = new ChildObj(); CopyObject(this, obj); } public override CopyObject(ChildObj source, ParentObj dest) { base.CopyObject(source, dest) dest.myB = source.myB; } } 

请注意,CopyObject()基本上是Object.MemberwiseClone(),假设您不仅仅是复制值,您还将克隆任何类的成员。

尝试使用以下[使用关键字“new”]

 public class Parent { private int _X; public int X{ set{_X=value;} get{return _X;}} public Parent copy() { return new Parent{X=this.X}; } } public class Child:Parent { private int _Y; public int Y{ set{_Y=value;} get{return _Y;}} public new Child copy() { return new Child{X=this.X,Y=this.Y}; } } 

您应该使用MemberwiseClone方法:

 public class ParentObj : ICloneable { protected int myA; public virtual Object Clone() { ParentObj newObj = this.MemberwiseClone() as ParentObj; newObj.myA = this.MyA; // not required, as value type (int) is automatically already duplicated. return newObj; } } public class ChildObj : ParentObj { protected int myB; public override Object Clone() { ChildObj newObj = base.Clone() as ChildObj; newObj.myB = this.MyB; // not required, as value type (int) is automatically already duplicated return newObj; } }