簡體   English   中英

我需要通過繼承實現C#深層復制構造函數。 有哪些模式可供選擇?

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

我希望在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等待中!

典型的方法是在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<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隨機類而不保證實現(除了遵循記錄的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);
}

警告:

使用此代碼時應格外小心。 使用風險自負。 本示例按原樣提供,不提供任何形式的保證。


還有另一種方法可以對對象圖執行深度克隆。 在考慮使用此示例時,請注意以下幾點很重要:

缺點:

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

現在已經足夠簡單了,它只是在克隆過程中為對象建立一個字典映射,並用不應克隆的任何對象填充它。 您會注意到提供給字典的比較器是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); }
}

這很容易,只需一個比較器就可以強制使用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;
    }
}

您會立即注意到數組和委托復制的特殊情況。 每個都有其自己的原因,首先Array沒有可克隆的“成員”,因此您必須處理此問題並依賴淺Clone()成員,然后克隆每個元素。 至於代表,它可能沒有特殊情況就可以工作。 但是,這將更加安全,因為它不會復制RuntimeMethodHandle之類的東西。 如果您打算在核心運行時中將其他內容(例如System.Type)包含在層次結構中,則建議您以類似的方式顯式地處理它們。

最后一種情況,也是最常見的,是簡單地使用大致是由BinaryFormatter的使用相同的程序。 這些使我們能夠從原始對象中彈出所有實例字段(公共或私有),進行克隆,然后將其粘貼到空對象中。 這里的好處是,GetUninitializedObject返回一個沒有在其上運行ctor的新實例,這可能會導致問題並降低性能。

上述方法是否有效將在很大程度上取決於您的特定對象圖及其中的數據。 如果您控制圖中的對象,並且知道它們沒有引用諸如Thread之類的愚蠢事物,那么上面的代碼應該可以很好地工作。

測試:

這是我最初測試時寫的內容:

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

最后說明:

老實說,當時這很有趣。 深入克隆數據模型通常是一件好事。 當今的現實是,大多數數據模型都是通過生成的深層克隆例程生成的,從而淘汰了上述黑客的用途。 我強烈建議您生成數據模型,並具有執行深度克隆的能力,而不是使用上面的代碼。

最好的方法是序列化對象,然后返回反序列化的副本。 除了標記為不可序列化的對象之外,它將拾取對象的所有內容,並使繼承序列化變得容易。

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

這不是最有表現力的事情,但替代方案也不是:反思。 此選項的好處是它可以無縫繼承。

  1. 您可以使用反射來循環所有變量並復制它們。(慢速)如果對您的軟件慢了,您可以使用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;
        }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM