代码之家  ›  专栏  ›  技术社区  ›  AnthonyLambert

我需要使用继承实现C深度复制构造函数。有哪些模式可供选择?

  •  13
  • AnthonyLambert  · 技术社区  · 15 年前

    我希望在C语言中实现类层次结构的deepcopy#

    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等待!

    8 回复  |  直到 9 年前
        1
  •  30
  •   Pavel Minaev    15 年前

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

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

    另一种方法是 Object.MemberwiseClone 在执行 Clone -这将确保结果始终为正确的类型,并允许覆盖扩展:

     class Base : ICloneable
     { 
         List<int> 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<int>(xs);
    
             return result;
         }
     }
    
     class Derived : Base
     { 
         List<int> 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<int>(ys);
    
             return result;
         }
     }
    

    这两种方法都要求层次结构中的所有类都遵循模式。哪一个是首选的。

    如果您只是有任何随机类实现 ICloneable 不保证实现(除了遵循 可逗留的 )无法扩展。

        2
  •  7
  •   Fernando    15 年前

    尝试序列化技巧:

    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);
    }
    
        3
  •  7
  •   csharptest.net    15 年前

    警告:

    使用此代码时应非常小心。自担风险使用。这个例子是按原样提供的,没有任何形式的保证。


    在对象图上执行深度克隆还有另一种方法。在考虑使用此样本时,务必注意以下几点:

    欺骗:

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

    那你为什么要用它呢?

    赞成的意见:

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

    代码使用:

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

    Class1 copy = Clone(myClass1);
    

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

    Class1 copy = Clone(myClass1, this);
    

    实施:

    现在让我们先把简单的东西拿开…这里是入口点:

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

    现在这已经足够简单了,它只是在克隆期间为对象构建一个字典映射,并用任何不应该被克隆的对象填充它。您将注意到字典中提供的比较器是一个引用比较器,让我们看看它的作用:

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

    这很简单,只是一个强制使用系统的比较器。现在是艰苦的工作:

    private static T InternalClone<T>(T input, Dictionary<object, object> 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;
        }
    }
    

    您将立即注意到数组和委托副本的特殊情况。每个都有自己的原因,第一个数组没有可以克隆的“members”,因此您必须处理这个问题,并依赖shallow clone()成员,然后克隆每个元素。至于代表呢 可以 没有特殊情况下工作;但是,这样做会更安全,因为它不会复制runtimemethodhandle之类的东西。如果您打算从核心运行时(如system.type)在层次结构中包含其他内容,我建议您以类似的方式显式地处理它们。

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

    上面的工作与否很大程度上取决于您的特定对象图和其中的数据。如果您控制了图中的对象,并且知道它们没有引用像线程这样的愚蠢的东西,那么上面的代码应该可以很好地工作。

    测试:

    以下是我最初写的测试方法:

    class Test
    {
        public Test(string name, params Test[] children)
        {
            Print = (Action<StringBuilder>)Delegate.Combine(
                new Action<StringBuilder>(delegate(StringBuilder sb) { sb.AppendLine(this.Name); }),
                new Action<StringBuilder>(delegate(StringBuilder sb) { sb.AppendLine(this.Name); })
            );
            Name = name;
            Children = children;
        }
        public string Name;
        public Test[] Children;
        public Action<StringBuilder> Print;
    }
    
    static void Main(string[] args)
    {
        Dictionary<string, Test> data2, data = new Dictionary<string, Test>(StringComparer.OrdinalIgnoreCase);
    
        Test a, b, c;
        data.Add("a", a = new Test("a", new Test("a.a")));
        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());
    }
    

    最后注意事项:

    老实说,那是一次有趣的锻炼。在数据模型上进行深度克隆通常是一件好事。今天的现实是,大多数数据模型都是由一个生成的深度克隆程序生成的,这会破坏上面黑客的有用性。我强烈建议您生成数据模型,它能够执行深度克隆,而不是使用上面的代码。

        4
  •  2
  •   jrista    15 年前

    最好的方法是序列化对象,然后返回反序列化副本。它将获取关于对象的所有信息,除了那些标记为不可序列化的信息,并使继承序列化变得容易。

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

    这不是最能发挥作用的事情,但也不是最好的选择:让步。这个选项的好处是它可以无缝继承。

        5
  •  0
  •   Peter    15 年前
    1. 您可以使用反射来循环所有变量并复制它们。(慢)如果它对您的软件变慢,您可以使用动态方法并生成IL。
    2. 序列化对象并再次反序列化。
        6
  •  0
  •   Chris Shaffer    15 年前

    我认为您在这里没有正确地实现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(),假定您所做的不仅仅是复制值,您还将克隆属于类的任何成员。

        7
  •  0
  •   Waleed A.K.    14 年前

    尝试使用以下内容[使用关键字“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};
      }
    }
    
        8
  •  -1
  •   tofi9    15 年前

    你应该使用 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;
            }
    }