1. 程式人生 > 其它 >C# 建立物件的副本(淺複製和深複製)

C# 建立物件的副本(淺複製和深複製)

C# 複製物件的副本

問題

您需要一種方法對可能引用其他型別的資料型別進行淺克隆操作、深克隆操作或者同時執行這兩種操作,但是不應該使用 ICloneable 介面,因為它違反了 .NET Framework 設計準則。

解決方法

為了解決使用 ICloneable 的問題,建立另外兩個介面 IShallowCopy 和 IDeepCopy 來建立一種複製模式,程式碼如下所示。

public interface IShallowCopy<T>  // 淺拷貝介面
{
 T ShallowCopy();
}

public interface IDeepCopy<T>  // 深拷貝介面
{
 T DeepCopy();
}

淺複製 意味著所複製物件的欄位將引用與原始物件相同的物件。為了允許進行淺複製,可在類中實現 IShallowCopy 介面,程式碼如下所示。

using System;
using System.Collections;
using System.Collections.Generic;
public class ShallowClone : IShallowCopy<ShallowClone>
{
public int Data = 1;
public List<string> ListData = new List<string>();
public object ObjData = new object();
public ShallowClone ShallowCopy() => (ShallowClone)this.MemberwiseClone();
}

深複製 (或稱克隆)意味著所複製物件的欄位將引用原始物件的欄位的新副本。為了進行深複製,可在類中實現 IDeepCopy 介面,程式碼如下所示。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
[Serializable] public class DeepClone : IDeepCopy<DeepClone>
{
public int data = 1;
public List<string> ListData = new List<string>();
public object objData = new object();
public DeepClone DeepCopy()
{
BinaryFormatter BF = new BinaryFormatter();
MemoryStream memStream = new MemoryStream();
BF.Serialize(memStream, this);
memStream.Flush();
memStream.Position = 0;
return (DeepClone)BF.Deserialize(memStream);
}
}

要同時支援淺複製和深複製方法,可同時實現這兩個介面,程式碼如下所示。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
[Serializable] public class MultiClone : IShallowCopy<MultiClone>, IDeepCopy<MultiClone>
{
public int data = 1;
public List<string> ListData = new List<string>();
public object objData = new object();
public MultiClone ShallowCopy() => (MultiClone)this.MemberwiseClone();
public MultiClone DeepCopy()
{
BinaryFormatter BF = new BinaryFormatter();
MemoryStream memStream = new MemoryStream();
BF.Serialize(memStream, this);
memStream.Flush();
memStream.Position = 0;
return (MultiClone)BF.Deserialize(memStream);
}
}

講解

.NET Framework 中包含一個名為 ICloneable 的介面,它最初被設計為在 .NET 中實現克隆的方法。設計建議現在不再在任何公開 API 中使用這個介面,因為它容易將自身導向不同的解釋。此介面看起來如下所示。
public interface ICloneable { object Clone(); }
注意此介面只有一個方法 Clone ,它返回一個物件。該克隆是物件的淺副本還是深副本呢?無法通過該介面得知這一點,因為實現可以選擇任何一個方式。這就是不應該再使用它,而是引入 IShallowCopy 和 IDeepCopy 介面的原因。
克隆 操作能夠創建出型別例項的一個準確副本(克隆)。克隆可能採用兩種形式之一:淺複製和深複製。淺複製相對容易一些,它對涉及複製的物件呼叫 ShallowCopy 方法。
在原始物件中,引用型別的欄位像值型別的欄位那樣進行復制。例如,如果原始物件包含一個 StreamWriter 型別的欄位,克隆的物件將指向原始物件的 StreamWriter 的同一個例項,並沒有建立新物件。
 在執行克隆操作時無需處理靜態欄位。每個應用程式域中的每個類的每個靜態欄位只會保留一個記憶體位置。克隆出的物件與原始物件訪問相同的靜態欄位。
對淺複製的支援是通過 Object 類的 MemberwiseClone 方法來實現的,Object 類充當了所有 .NET 類的基類。因此,下面的程式碼通過 Clone 方法允許建立和返回一個淺複製。
public ShallowClone ShallowCopy() => (ShallowClone)this.MemberwiseClone();
克隆一個物件的另一種方式是建立深複製。就像淺複製那樣,深複製將建立原始物件的一個副本。不同的是,深複製還會建立原始物件中每個引用型別的欄位的單獨副本。因此,如果原始物件包含一個 StreamWriter 型別的欄位,複製的物件也會包含一個 StreamWriter 型別的欄位,但是複製物件的 StreamWriter 欄位將指向一個新的 StreamWriter 物件,而不是原始物件的 StreamWriter 物件。
.NET Framework 沒有直接提供對深複製的支援,但是下面的程式碼提供了一種實現深複製的簡單方式。
BinaryFormatter BF = new BinaryFormatter(); MemoryStream memStream = new MemoryStream(); BF.Serialize(memStream, this); memStream.Flush(); memStream.Position = 0; return (BF.Deserialize(memStream));
總而言之,這使用二進位制序列化將原始物件序列化到一個記憶體流中,然後將其反序列化到一個新物件中,並將該物件返回給呼叫者。在呼叫 Deserialize 方法之前將記憶體流指標重新定位到流的開始處是十分重要的;否則就會引發一個異常,指示序列化的物件中不包含任何資料。
使用物件序列化執行深複製時,不必修改執行深複製的程式碼就能改下層的物件。如果您手動執行深複製,就必須對原始物件的每個例項欄位建立新例項,並把新例項複製到克隆的物件。這是一件非常瑣碎的事情。如果修改了原始物件的欄位,您必須修改深複製的程式碼以反映出這些修改。使用序列化可以依靠序列化器動態查詢和序列化物件中包含的所有欄位。如果修改了物件,序列化器仍然不需要修改就可以進行深複製。
您可能想手動執行深複製的一個原因是,僅當物件中的一切都可序列化時,本範例中介紹的序列化技術才會正確工作。當然,手動複製有時也於事無補,因為有些物件天生就是不可複製的。假設您有一個網路管理應用,其中一個物件代表網路上的一臺特定印表機。當您複製它時會指望它做什麼呢?傳真一份訂購單以購買一臺新的印表機嗎?
深複製與生俱來的一個問題是在具有迴圈引用的巢狀資料結構上執行深複製。本範例使得處理迴圈引用成為可能,儘管這仍是一個難題。因此,事實上,如果您使用本範例中的方法,就無需避免迴圈引用。

參考原始碼

以下是本文用到的完整程式碼:


        #region "Building Cloneable Classes"
using System;
using System.Runtime.InteropServices;
using System.Globalization;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Text;
using System.Collections.ObjectModel;

        public interface IShallowCopy<T>
        {
            T ShallowCopy();
        }
        public interface IDeepCopy<T>
        {
            T DeepCopy();
        }
        public class ShallowClone : IShallowCopy<ShallowClone>
        {
            public int Data = 1;
            public List<string> ListData = new List<string>();
            public object ObjData = new object();
            public ShallowClone ShallowCopy() => (ShallowClone)this.MemberwiseClone();
        }
        [Serializable]
        public class DeepClone : IDeepCopy<DeepClone>
        {
            public int data = 1;
            public List<string> ListData = new List<string>();
            public object objData = new object();
            public DeepClone DeepCopy()
            {
                BinaryFormatter BF = new BinaryFormatter();
                MemoryStream memStream = new MemoryStream();
                BF.Serialize(memStream, this);
                memStream.Flush();
                memStream.Position = 0;
                return (DeepClone)BF.Deserialize(memStream);
            }
        }
        [Serializable]
        public class MultiClone : IShallowCopy<MultiClone>,
                                  IDeepCopy<MultiClone>
        {
            public int data = 1;
            public List<string> ListData = new List<string>();
            public object objData = new object();
            public MultiClone ShallowCopy() => (MultiClone)this.MemberwiseClone();
            public MultiClone DeepCopy()
            {
                BinaryFormatter BF = new BinaryFormatter();
                MemoryStream memStream = new MemoryStream();
                BF.Serialize(memStream, this);
                memStream.Flush();
                memStream.Position = 0;
                return (MultiClone)BF.Deserialize(memStream);
            }
        }
        #endregion