1. 程式人生 > 實用技巧 >C#深度複製和淺度複製

C#深度複製和淺度複製

C#深度複製和淺度複製

複製一個值變數很簡單,新建一個變數然後將原來的變數賦值過去就行,但是複製一個引用變數這種方法是不行的,如果不明白為什麼可以先看看這篇解釋
引用型別變數和值型別變數在賦值時的不同

如果要複製一個引用型別的變數,比如說類,需要在類定義中繼承ICloneable介面,並實現Clone方法,這是一個固定格式,下面看一個例子

 public class Test:ICloneable
 {
     public int Val;
     public object Clone() => MemberwiseClone();
 }

定義了一個Test類,繼承ICloneable介面,實現Clone方法,實現Clone方法的格式是固定的,這裡使用了簡化寫法,public object Clone() => MemberwiseClone();

等於

public object Clone()
{
    return MemberwiseClone();
}

這是C#提供的方法,按照這麼寫就對了,此時如果要複製一個Test類的引用變數,就可以這麼寫

Test t1 = new Test();
Test t2 = (Test)t1.Clone();

有一個引用型別變數t1,呼叫t1.Clone()會在堆中重新開闢一個記憶體空間,並且複製t1堆空間的值,然後會返回這個新空間的記憶體地址,因為Clone()方法返回型別是object,所以強制型別轉換為Test,然後賦給Test型別的變數t2

這個過程,便是C#中的淺度複製(ShallowCopy),也有稱為影子複製的
這個複製會存在一些問題,那就是一個引用型別中存在引用型別欄位時,引用型別欄位並不會也複製一份

public class Test:ICloneable
{
    public int Val;
    public Test2 Z = new Test2();
    public object Clone()
    {
        return MemberwiseClone();
    }
}
public class Test2
{
    public int D;
}

這裡定義了兩個類,其中Test類中包含了一個Test2型別的引用型別變數Z,我們先看記憶體中怎樣表示的
從這張圖我們可以看到,當我們使用Clone對引用型別進行Clone時,只會複製堆空間的值,如果堆空間中有引用型別,在複製時就只單純的複製了引用型別的堆空間地址,這樣的後果就是雖然Clone得到了兩個堆空間物件,但是堆空間物件中的欄位卻指向了同一個另外的空間地址,在某些情況下就會出現問題
所以
淺度複製:解決了直接使用賦值運算子時變數指向了同一個堆空間的問題(即Test t2 = t1的問題),但是隻解決了一層,對於包含在內的堆空間中的成員的引用沒有進一步解決(即t1.Z和t2.Z指向了同一個堆空間地址)

為了解決上述問題,就需要使用深度複製,深度複製的基本思路,就是在Clone方法中建立一個新的物件並使其內容與現有內容保持一致(其實是廢話),我們甚至可以不用繼承ICloneable,但是建議繼承並使用Clone方法,這樣可以保持方法名的一致性
至於怎麼實現,可以自行思考(主要是我也在思考中)

所以這篇文章的主要內容是講:C#提供的return MemberwiseClone();只是淺度複製,不會為引用成員建立新空間並將引用成員空間的值複製過去,只會複製引用成員的空間地址,需要注意