简体   繁体   English

我需要通过继承实现C#深层复制构造函数。 有哪些模式可供选择?

[英]I need to implement C# deep copy constructors with inheritance. What patterns are there to choose from?

I wish to implement a deepcopy of my classes hierarchy in 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;
        }
}

This will not work as when Cloning the Child only a parent is new-ed. 这与克隆“仅子级的孩子”时不起作用。 In my code some classes have large hierarchies. 在我的代码中,某些类具有较大的层次结构。

What is the recommended way of doing this? 建议这样做的方法是什么? Cloning everything at each level without calling the base class seems wrong? 克隆每个级别的所有内容而不调用基类似乎是错误的? There must be some neat solutions to this problem, what are they? 对于这个问题必须有一些巧妙的解决方案,它们是什么?

Can I thank everyone for their answers. 我可以感谢大家的回答。 It was really interesting to see some of the approaches. 看到其中一些方法真的很有趣。 I think it would be good if someone gave an example of a reflection answer for completeness. 我认为,如果有人举一个完整的反思性回答的例子,那会很好。 +1 awaiting! +1等待中!

The typical approach is to use "copy constructor" pattern a la C++: 典型的方法是在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);
     }
 }

The other approach is to use Object.MemberwiseClone in the implementation of Clone - this will ensure that result is always of the correct type, and will allow overrides to extend: 另一种方法是在Clone的实现中使用Object.MemberwiseClone这将确保结果始终是正确的类型,并将允许覆盖扩展:

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

Both approaches require that all classes in the hierarchy follow the pattern. 两种方法都要求层次结构中的所有类都遵循该模式。 Which one to use is a matter of preference. 使用哪个是优先选择的问题。

If you just have any random class implementing ICloneable with no guarantees on implementation (aside from following the documented semantics of ICloneable ), there's no way to extend it. 如果您只有任何实现ICloneable随机类而不保证实现(除了遵循记录的ICloneable语义),就无法扩展它。

try the serialization trick: 尝试序列化技巧:

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

WARNING: 警告:

This code should be used with a great deal of caution. 使用此代码时应格外小心。 Use at your own risk. 使用风险自负。 This example is provided as-is and without a warranty of any kind. 本示例按原样提供,不提供任何形式的保证。


There is one other way to perform a deep clone on an object graph. 还有另一种方法可以对对象图执行深度克隆。 It is important to be aware of the following when considering using this sample: 在考虑使用此示例时,请注意以下几点很重要:

Cons: 缺点:

  1. Any references to external classes will also be cloned unless those references are provided to the Clone(object, ...) method. 除非提供给Clone(object,...)方法,否则对外部类的任何引用也将被克隆。
  2. No constructors will be executed on cloned objects they are reproduced EXACTLY as they are. 不会对复制的对象按原样精确复制它们的构造函数。
  3. No ISerializable or serialization constructors will be executed. 不会执行ISerializable或序列化构造函数。
  4. There is no way to alter the behavior of this method on a specific type. 无法更改此方法在特定类型上的行为。
  5. It WILL clone everything, Stream, AppDomain, Form, whatever, and those will likely break your application in horrific ways. 它会克隆所有内容,包括Stream,AppDomain,Form等,而这些都可能以可怕的方式破坏您的应用程序。
  6. It could break whereas using the serialization method is much more likely to continue working. 它可能会中断,而使用序列化方法则更有可能继续工作。
  7. The implementation below uses recursion and can easily cause a stack overflow if your object graph is too deep. 下面的实现使用递归,如果对象图太深,很容易导致堆栈溢出。

So why would you want to use it? 那为什么要使用它呢?

Pros: 优点:

  1. It does a complete deep-copy of all instance data with no coding required in the object. 它可以对所有实例数据进行完整的深拷贝,而无需在对象中进行编码。
  2. It preserves all object graph references (even circular) in the reconstituted object. 它将所有对象图引用(甚至圆形)保留在重构的对象中。
  3. It's executes more than 20 times fatser than the binary formatter with less memory consumption. 它执行的速度比二进制格式化程序高20倍,而内存消耗更少。
  4. It requires nothing, no attributes, implemented interfaces, public properties, nothing. 它不需要任何内容​​,没有属性,实现的接口,公共属性也没有任何内容。

Code Usage: 代码用法:

You just call it with an object: 您只需用一个对象调用它:

Class1 copy = Clone(myClass1);

Or let's say you have a child object and you are subscribed to it's events... Now you want to clone that child object. 或假设您有一个子对象,并且您已订阅了该对象的事件...现在您想克隆该子对象。 By providing a list of objects to not clone, you can preserve some potion of the object graph: 通过提供克隆对象的列表,可以保留部分对象图:

Class1 copy = Clone(myClass1, this);

Implementation: 实现方式:

Now let's get the easy stuff out of the way first... Here is the entry point: 现在,让我们首先简单地解决问题...这是切入点:

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

Now that is simple enough, it just builds a dictionary map for the objects during the clone and populates it with any object that should not be cloned. 现在已经足够简单了,它只是在克隆过程中为对象建立一个字典映射,并用不应克隆的任何对象填充它。 You will note the comparer provided to the dictionary is a ReferenceComparer, let's take a look at what it does: 您会注意到提供给字典的比较器是ReferenceComparer,让我们看一下它的作用:

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

That was easy enough, just a comparer that forces the use of the System.Object's get hash and reference equality... now comes the hard work: 这很容易,只需一个比较器就可以强制使用System.Object的get哈希和引用相等...现在来了:

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

You will notice right-off the special case for array and delegate copy. 您会立即注意到数组和委托复制的特殊情况。 Each have their own reasons, first Array does not have 'members' that can be cloned, so you have to handle this and depend on the shallow Clone() member and then clone each element. 每个都有其自己的原因,首先Array没有可克隆的“成员”,因此您必须处理此问题并依赖浅Clone()成员,然后克隆每个元素。 As for the delegate it may work without the special-case; 至于代表,它可能没有特殊情况就可以工作。 however, this will be far safer since it's not duplicating things like RuntimeMethodHandle and the like. 但是,这将更加安全,因为它不会复制RuntimeMethodHandle之类的东西。 If you intend to include other things in your hierarchy from the core runtime (like System.Type) I suggest you handle them explicitly in similar fashion. 如果您打算在核心运行时中将其他内容(例如System.Type)包含在层次结构中,则建议您以类似的方式显式地处理它们。

The last case, and most common, is simply to use roughly the same routines that are used by the BinaryFormatter. 最后一种情况,也是最常见的,是简单地使用大致是由BinaryFormatter的使用相同的程序。 These allow us to pop all the instance fields (public or private) out of the original object, clone them, and stick them into an empty object. 这些使我们能够从原始对象中弹出所有实例字段(公共或私有),进行克隆,然后将其粘贴到空对象中。 The nice thing here is that the GetUninitializedObject returns a new instance that has not had the ctor run on it which could cause issues and slow the performance. 这里的好处是,GetUninitializedObject返回一个没有在其上运行ctor的新实例,这可能会导致问题并降低性能。

Whether the above works or not will highly depend upon your specific object graph and the data therein. 上述方法是否有效将在很大程度上取决于您的特定对象图及其中的数据。 If you control the objects in the graph and know that they are not referencing silly things like a Thread then the above code should work very well. 如果您控制图中的对象,并且知道它们没有引用诸如Thread之类的愚蠢事物,那么上面的代码应该可以很好地工作。

Testing: 测试:

Here is what I wrote to originally test this: 这是我最初测试时写的内容:

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

Final Note: 最后说明:

Honestly it was a fun exercise at the time. 老实说,当时这很有趣。 It is generally a great thing to have deep cloning on a data model. 深入克隆数据模型通常是一件好事。 Today's reality is that most data models are generated which obsoletes the usefulness of the hackery above with a generated deep clone routine. 当今的现实是,大多数数据模型都是通过生成的深层克隆例程生成的,从而淘汰了上述黑客的用途。 I highly recommend generating your data model & it's ability to perform deep-clones rather than using the code above. 我强烈建议您生成数据模型,并具有执行深度克隆的能力,而不是使用上面的代码。

The best way is by serializing your object, then returning the deserialized copy. 最好的方法是序列化对象,然后返回反序列化的副本。 It will pick up everything about your object, except those marked as non-serializable, and makes inheriting serialization easy. 除了标记为不可序列化的对象之外,它将拾取对象的所有内容,并使继承序列化变得容易。

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

It is not the most performant thing, but neither is the alternative: relection. 这不是最有表现力的事情,但替代方案也不是:反思。 The benefit of this option is that it seamlessly inherits. 此选项的好处是它可以无缝继承。

  1. You could use reflection to loop all variables and copy them.(Slow) if its to slow for you software you could use DynamicMethod and generate il. 您可以使用反射来循环所有变量并复制它们。(慢速)如果对您的软件慢了,您可以使用DynamicMethod并生成il。
  2. serialize the object and deserialize it again. 序列化对象并再次反序列化。

I don't think you are implementing ICloneable correctly here; 我认为您在这里无法正确实现ICloneable; It requires a Clone() method with no parameters. 它需要没有参数的Clone()方法。 What I would recommend is something like: 我推荐的是这样的:

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

Note that CopyObject() is basically Object.MemberwiseClone(), presumeably you would be doing more than just copying values, you would also be cloning any members that are classes. 请注意,CopyObject()基本上是Object.MemberwiseClone(),想必您将做的不仅仅是复制值,还可以克隆作为类的任何成员。

Try to use the following [use the keyword "new"] 尝试使用以下[使用关键字“ 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};
  }
}

You should use the MemberwiseClone method instead: 您应该改用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;
        }
}

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

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