[C#] 014.正確實現淺層複製和深層複製 (欄位與類別被Clone時的差異)

讀【157個完美化C#的建議】一書的理解筆記 - 014

重點:在類別建立後,呼叫.clone() 函式只會對該類別的欄位記憶體位址複製,而沒有將類別內的類別記憶體進行複製。

           因此用.clone()時需要改變原本寫法

流程說明
1. 淺層複製範例 (Shallow Copy)
2. 深層複製範例 (Deep Copy)
3. 整合淺層、深層複製的實作範例
4. 結論

1. 淺層複製範例

我們宣告以下類別,並且實作函式 Clone()

/// <summary>
/// 淺層複製的實作
/// </summary>
public class employee : ICloneable
{
        /// <summary>
        /// 身分證
        /// </summary>
        public string IDCode { get; set; }

        /// <summary>
        /// 年齡
        /// </summary>
        public int age { get; set; }

        /// <summary>
        /// 部門
        /// </summary>
        public Department dept {get;set;}

        /// <summary>
        /// 淺層複製
        /// </summary>
        /// <returns></returns>
        public object Clone()
        {
           //建立物件的淺層複製
           return this.MemberwiseClone();
        }
}
/// <summary>
/// 部門的類別
/// </summary>
[Serializable]
public class Department
{
      public string Name { get; set;}

      /// <summary>
      /// 覆寫此類別的引用ToString()
      /// </summary>
      /// <returns></returns>
      public override string ToString()
      {
           return this.Name;
      }

}

在主程式有以下,建立一個Louis 的資料,並且建立Alise複製Louis的資料

可以看到Alise初始資料與Louis 完全相同

//淺層複製呼叫
employee Louis = new employee()
{
     age = 29,
     dept = new Department() { Name = "軟體開發部" },
     IDCode = "H333456789"
};
//=================   淺層複製  =====================
employee Alise = Louis.Clone() as employee;
//此時Alise資料如下:
var alise_age = Alise.age;//-------------  29
var alise_IDcode = Alise.IDCode;//-------  H333456789
var alise_dept_Name = Alise.dept;//   軟體開發部

接著我們改變Alise資料會發現類別的部分沒有異動,發現淺層複製有以下特性

1. 對欄位建立新的記憶體位址

2. 對類別參考記憶體位址

//我們嘗試改變最源頭的Louis的個人資料
Louis.age = 44;
Louis.IDCode = "我沒身分證";
Louis.dept.Name = "銷售部";

//改變Louis資料後,再次觀察Alise的資料如下:
var alise_age2 = Alise.age;//-------   29
var alise_IDcode2 = Alise.IDCode;//-   H333456789
var alise_dept_Name2 = Alise.dept;//   銷售部

 

2. 深層複製範例

相對於淺層複製,深層複製就是完全New新的物件了,建立以下類別

※請加入Attribute  [Serializable] 

/// <summary>
/// 深層複製的實作
/// </summary>
[Serializable]
 public class employeeDeep 
{
    public string IDCode { get; set; }

    public int age { get; set; }

    public Department dept { get; set; }

    
    public object Clone()
    {

        using (Stream objectStream = new MemoryStream())
        {
            //序列化物件格式
            IFormatter formatter = new BinaryFormatter();
            //將自己所有資料序列化
            formatter.Serialize(objectStream, this);
            //複寫資料流位置,返回最前端
            objectStream.Seek(0, SeekOrigin.Begin);
            //再將objectStream反序列化回去 
            return formatter.Deserialize(objectStream) as employeeDeep;
        }


    }
}

我們在一次建立Louis2 類別,讓CoCo繼承,改變源頭的Louis_2 部門dept的資料,可以發現CoCo的部門還是銷售部了。

//===========  深層複製 ※把上面的測試再來一次,但使用我們的深層複製
//※記得在所有相關的地方標上 [Serializable]
employeeDeep Louis_2 = new employeeDeep()
{
    age = 29,
    dept = new Department() { Name = "軟體開發部" },
    IDCode = "H333456789"
};
//=================   深層複製  =====================
employeeDeep CoCo = Louis_2.Clone() as employeeDeep;
//此時Alise資料如下:
var CoCo_age = CoCo.age;//-------------  29
var CoCo_IDcode = CoCo.IDCode;//-------  H333456789
var CoCo_dept_Name = CoCo.dept;//   軟體開發部

//我們嘗試改變最源頭的Louis_2的個人資料
Louis_2.age = 44;
Louis_2.IDCode = "我沒身分證";
Louis_2.dept.Name = "銷售部";

//改變Louis資料後,再次觀察CoCo的資料如下:
var CoCo_age2 = CoCo.age;//-------   29
var CoCo_IDcode2 = CoCo.IDCode;//-   H333456789
var CoCo_dept_Name2 = CoCo.dept;//   軟體開發部 (沒有被Louis_2影響)

 

3. 整合淺層、深層複製的實作範例

如果需要很彈性的淺層複製與深層複製並行,以下是整合的做法(※分別做深層,淺層)

//=====整合版
/// <summary>
/// 深層複製 + 淺層複製的實作
/// </summary>
[Serializable]
public class employeeCombinate : ICloneable
{
    public string IDCode { get; set; }

    public int age { get; set; }

    public Department dept { get; set; }

    /// <summary>
    /// 深層複製 - 深層要獨立出來
    /// </summary>
    /// <returns></returns>
    public employeeCombinate DeepClone()
    {
        using (Stream objectStream = new MemoryStream())
        {
            //序列化物件格式
            IFormatter formatter = new BinaryFormatter();
            //將自己所有資料序列化
            formatter.Serialize(objectStream, this);
            //複寫資料流位置,返回最前端
            objectStream.Seek(0, SeekOrigin.Begin);
            //再將objectStream反序列化回去 
            return formatter.Deserialize(objectStream) as employeeCombinate;
        }


    }

    /// <summary>
    /// 淺層複製
    /// </summary>
    /// <returns></returns>
    public object Clone()
    {
        return this.MemberwiseClone();
    }
}

 

4. 結論

淺層複製與深層複製的複製差異為Object物件,如果有封裝的物件在淺層複製不會New 新的物件。

深層複製的應用使得我們可以快速複製一個新的獨立類別物件。

github連結(Vs2015) : 點我下載