0. 一般使用狀況(無使用任何深層複製)
```C#
public class Item
{
public string Name { get; set; }
public int Number { get; set; }
public List Features { get; set; }
}
class Program
{
static void Main(string[] args)
{
var source = new Item { Name = "The Lord of the Rings", Number = 10, Features = new List { "Love", "Funny" } };
var clone = source;
clone.Name = "Harry Potter";
clone.Number = 30;
clone.Features[0] = "Adventure";
clone.Features[1] = "Magic";
Console.WriteLine("Source" + Environment.NewLine + "Name: "+ source.Name + Environment.NewLine + "Number: " + source.Number + Environment.NewLine + "Features: " + string.Join(", ", source.Features));
Console.WriteLine();
Console.WriteLine("Clone" + Environment.NewLine + "Name: " + clone.Name + Environment.NewLine + "Number: " + clone.Number + Environment.NewLine + "Features: " + string.Join(", ", clone.Features));
}
}
/* 輸出結果(原始資料被更動了) */
//Source Object
//Name: Harry Potter
//Number: 30
//Features: Adventure, Magic
//
//Clone Object
//Name: Harry Potter
//Number: 30
//Features: Adventure, Magic
```
1. 使用 BinaryFormatter 複製
優點: 幾乎可完美處理任何複雜的物件,網路上查到的大多也都是這個方式
缺點: 執行時間較其他方式長,複製對象 Class 必需標記 [Serializable] 標籤有點麻煩
先建立一個擴充方法如下 ```C# using System; using System.IO; using System.Runtime.Serialization.Formatters.Binary; public static class CommonExtensions { ///
/// 深層複製(複製對象必需可序列化)
///
/// 複製對象類別
/// 複製對象
/// 複製出的物件
public static T DeepClone<T>(this T source)
{
if (!typeof(T).IsSerializable)
{
throw new ArgumentException("The type must be serializable.", nameof(source));
}
if (source != null)
{
using (MemoryStream stream = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(stream, source);
stream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(stream);
}
}
return default(T);
}
}
```
使用方式如下
```C#
[Serializable]
public class Item
{
public string Name { get; set; }
public int Number { get; set; }
public List Features { get; set; }
}
class Program
{
static void Main(string[] args)
{
var source = new Item { Name = "The Lord of the Rings", Number = 10, Features = new List { "Love", "Funny" } };
var clone = source.DeepClone();
clone.Name = "Harry Potter";
clone.Number = 30;
clone.Features[0] = "Adventure";
clone.Features[1] = "Magic";
Console.WriteLine("Source" + Environment.NewLine + "Name: "+ source.Name + Environment.NewLine + "Number: " + source.Number + Environment.NewLine + "Features: " + string.Join(", ", source.Features));
Console.WriteLine();
Console.WriteLine("Clone" + Environment.NewLine + "Name: " + clone.Name + Environment.NewLine + "Number: " + clone.Number + Environment.NewLine + "Features: " + string.Join(", ", clone.Features));
}
}
/* 輸出結果(原始資料不變) */
//Source Object
//Name: The Lord of the Rings
//Number: 10
//Features: Love, Funny
//
//Clone Object
//Name: Harry Potter
//Number: 30
//Features: Adventure, Magic
```
2. 使用 Newtonsoft.Json 套件
優點: 輕巧、簡單、速度比 BinaryFormatter 快上不少,基本上不知道要挑哪一個方式又想自己寫時,我會傾向建議使用此方式
缺點: Private 欄位無法被複製到,遇到循環參考會出錯(解決方式可參考[這篇](https://dotblogs.com.tw/wasichris/2015/12/03/152540))
建立一個擴充方法如下 ```C# using System; using Newtonsoft.Json; public static class CommonExtensions { ///
/// 深層複製
///
/// 複製對象類別
/// 複製對象
/// 複製出的物件
public static T DeepCloneByJson<T>(this T source)
{
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
var deserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}
}
```
使用方式如下
```C#
public class Item
{
public string Name { get; set; }
public int Number { get; set; }
public List Features { get; set; }
}
class Program
{
static void Main(string[] args)
{
var source = new Item { Name = "The Lord of the Rings", Number = 10, Features = new List { "Love", "Funny" } };
var clone = source.DeepCloneByJson();
clone.Name = "Harry Potter";
clone.Number = 30;
clone.Features[0] = "Adventure";
clone.Features[1] = "Magic";
Console.WriteLine("Source" + Environment.NewLine + "Name: "+ source.Name + Environment.NewLine + "Number: " + source.Number + Environment.NewLine + "Features: " + string.Join(", ", source.Features));
Console.WriteLine();
Console.WriteLine("Clone" + Environment.NewLine + "Name: " + clone.Name + Environment.NewLine + "Number: " + clone.Number + Environment.NewLine + "Features: " + string.Join(", ", clone.Features));
}
}
/* 輸出結果(原始資料不變) */
//Source Object
//Name: The Lord of the Rings
//Number: 10
//Features: Love, Funny
//
//Clone Object
//Name: Harry Potter
//Number: 30
//Features: Adventure, Magic
```
3. 使用 Expression Trees 方式([參考來源](https://www.codeproject.com/Articles/1111658/Fast-Deep-Copy-by-Expression-Trees-C-Sharp))
優點: 速度又比第二種方式更快,也可處理 Private 欄位、循環參考的問題
缺點: Framework 需 .NET 4 以上,我有遇到當型態是 `IEnumerable<T>` 時,會跳出錯誤(目前我是先轉 `List<T>` 型態解決),除此之外,還沒遇到其它問題
由於該擴充方法的程式碼有點冗長,所以這邊直接提供[原始碼檔案下載](https://drive.google.com/file/d/1fDE1CmIiadQLD9Ip42BWVs4_zPve28en/view?usp=sharing),然後使用方式參考下面 ```C# public class Item { public string Name { get; set; } public int Number { get; set; } public List Features { get; set; }
}
class Program
{
static void Main(string[] args)
{
var source = new Item { Name = "The Lord of the Rings", Number = 10, Features = new List { "Love", "Funny" } };
var clone = source.DeepCopyByExpressionTree();
clone.Name = "Harry Potter";
clone.Number = 30;
clone.Features[0] = "Adventure";
clone.Features[1] = "Magic";
Console.WriteLine("Source Object" + Environment.NewLine + "Name: "+ source.Name + Environment.NewLine + "Number: " + source.Number + Environment.NewLine + "Features: " + string.Join(", ", source.Features));
Console.WriteLine();
Console.WriteLine("Clone Object" + Environment.NewLine + "Name: " + clone.Name + Environment.NewLine + "Number: " + clone.Number + Environment.NewLine + "Features: " + string.Join(", ", clone.Features));
}
}
/* 輸出結果(原始資料不變) */
//Source Object
//Name: The Lord of the Rings
//Number: 10
//Features: Love, Funny
//
//Clone Object
//Name: Harry Potter
//Number: 30
//Features: Adventure, Magic
```
4. 在 Class 裡手動撰寫 DeepCopy 和 ShallowCopy 方法
優點: 速度最快,假設有其他更快的方式,請告知我,感恩
缺點: 必須撰寫較多的程式碼,後續維護較麻煩且容易有人為上的疏失(假如你夠細心的話可無視)
ShallowCopy 微軟提供了內建方法`Object.MemberwiseClone()`供呼叫,和 DeepCopy 的差意主要是前者只複製實值(Value)型別的欄位,如果是參考(reference)型別的欄位,則只複製該物件欄位的參考位址,所以該欄位的值還是屬於和大家共用的狀態,至於什麼是參考型別還實值型別,由於不在本篇討論的範圍,有興趣的讀者可以自行參考這兩篇文章:[一、Value Type(實值型別) vs Reference Type(參考型別)](https://xingulin.tumblr.com/post/48493582986/ref-type-vs-val-type)、[二、實值型別與參考型別的記憶體配置](https://dotblogs.com.tw/h091237557/2014/05/26/145247) 這邊分別比較使用這兩種方式的結果 ```C# public class Item { public Item ShallowCopy() { return (Item)this.MemberwiseClone(); } public Item DeepCopy() { var other = (Item)this.MemberwiseClone(); other.Name = String.Copy(Name); other.Features = new List();
foreach (var m in Features)
{
other.Features.Add(m);
}
return other;
}
public string Name { get; set; }
public int Number { get; set; }
public List Features { get; set; }
}
class Program
{
static void Main(string[] args)
{
/* ShallowCopy */
var source = new Item { Name = "The Lord of the Rings", Number = 10, Features = new List { "Love", "Funny" } };
var clone = source.ShallowCopy();
clone.Name = "Harry Potter";
clone.Name = "Harry Potter";
clone.Number = 30;
clone.Features[0] = "Adventure";
clone.Features[1] = "Magic";
Console.WriteLine("*** ShallowCopy ***");
Console.WriteLine("Source Object" + Environment.NewLine + "Name: "+ source.Name + Environment.NewLine + "Number: " + source.Number + Environment.NewLine + "Features: " + string.Join(", ", source.Features));
Console.WriteLine();
Console.WriteLine("Clone Object" + Environment.NewLine + "Name: " + clone.Name + Environment.NewLine + "Number: " + clone.Number + Environment.NewLine + "Features: " + string.Join(", ", clone.Features));
/* DeepCopy */
source = new Item { Name = "The Lord of the Rings", Number = 10, Features = new List { "Love", "Funny" } };
clone = source.DeepCopy();
clone.Name = "Harry Potter";
clone.Number = 30;
clone.Features[0] = "Adventure";
clone.Features[1] = "Magic";
Console.WriteLine(Environment.NewLine + "*** DeepCopy ***");
Console.WriteLine("Source Object" + Environment.NewLine + "Name: " + source.Name + Environment.NewLine + "Number: " + source.Number + Environment.NewLine + "Features: " + string.Join(", ", source.Features));
Console.WriteLine();
Console.WriteLine("Clone Object" + Environment.NewLine + "Name: " + clone.Name + Environment.NewLine + "Number: " + clone.Number + Environment.NewLine + "Features: " + string.Join(", ", clone.Features));
}
}
/* 輸出結果 */
//*** ShallowCopy(原物件的實值型別欄位沒有被更動,但欄位Features的值被更動了) ***
//Source Object
//Name: The Lord of the Rings
//Number: 10
//Features: Adventure, Magic
//
//Clone Object
//Name: Harry Potter
//Number: 30
//Features: Adventure, Magic
//*** DeepCopy(原物件的欄位都沒有被更動) ***
//Source Object
//Name: The Lord of the Rings
//Number: 10
//Features: Love, Funny
//
//Clone Object
//Name: Harry Potter
//Number: 30
//Features: Adventure, Magic
```
到這邊我猜有些讀者應該和我一樣,會有一個疑問就是`string`也是參考型別,但在執行 ShallowCopy 的時候,怎麼 source 物件的 Name 屬性確沒有被修改到,抱著該疑問查找了一下資料後,原因主要在於 CLR `string` 屬於不可變變數(Immutable),一但宣告建立後就不可再改變或重組,所以當變更 clone 物件的 Name 值時,實際上是重新分配了一塊記憶體給 clone 的 Name 屬性使用,造成兩個物件的 Name 屬性已指向不同的記憶體區塊
5. Nuget 上的 DeepCloner 套件([原始碼](https://github.com/force-net/DeepCloner))
優點: 速度比使用 Expression Trees 方式還快一些,懶得自己寫程式碼時,是一個還不錯的選擇,官方文件也有提供和各種方式的效能比較
缺點: 需另外安裝額外的套件,且需 .NET Framework 4 以上,不過它有支援 .Net Standard 算是一種優點吧!?
使用前需先從 Nuget 上安裝,使用方式如下 ```C# public class Item { public string Name { get; set; } public int Number { get; set; } public List Features { get; set; }
}
class Program
{
static void Main(string[] args)
{
var source = new Item { Name = "The Lord of the Rings", Number = 10, Features = new List { "Love", "Funny" } };
var clone = source.DeepClone();
clone.Name = "Harry Potter";
clone.Number = 30;
clone.Features[0] = "Adventure";
clone.Features[1] = "Magic";
Console.WriteLine("Source Object" + Environment.NewLine + "Name: "+ source.Name + Environment.NewLine + "Number: " + source.Number + Environment.NewLine + "Features: " + string.Join(", ", source.Features));
Console.WriteLine();
Console.WriteLine("Clone Object" + Environment.NewLine + "Name: " + clone.Name + Environment.NewLine + "Number: " + clone.Number + Environment.NewLine + "Features: " + string.Join(", ", clone.Features));
}
}
/* 輸出結果(原始資料不變) */
//Source Object
//Name: The Lord of the Rings
//Number: 10
//Features: Love, Funny
//
//Clone Object
//Name: Harry Potter
//Number: 30
//Features: Adventure, Magic
```
**總結**
經我自己測試後,大概整理出以下的心得 效能(處理時間): 4 > 5 > 3 > 2 > 1 實用性(之後我專案傾向採用的機率來排序): 5 > 2 = 3 > 1 > 4 事實上,真的很難挑出一個十全十美的處理方式,我認為選擇出一種最適合目前手上專案的才是最重要的
##參考資料 [\[搞搞就懂\] 深層複製(DeepClone)功能實作及應用](https://dotblogs.com.tw/wasichris/2015/12/03/152540) [\[史丹利好熱\] 物件建立之淺層複製vs深層複製](https://dotblogs.com.tw/stanley14/2017/03/28/shallow_and_deep_copy) [\[Stackoverflow\] Deep cloning objects](https://stackoverflow.com/questions/78536/deep-cloning-objects) [MSDN 的 Object.MemberwiseClone Method](https://docs.microsoft.com/en-us/dotnet/api/system.object.memberwiseclone?redirectedfrom=MSDN&view=netframework-4.8#System_Object_MemberwiseClone)
優點: 幾乎可完美處理任何複雜的物件,網路上查到的大多也都是這個方式
缺點: 執行時間較其他方式長,複製對象 Class 必需標記 [Serializable] 標籤有點麻煩
先建立一個擴充方法如下 ```C# using System; using System.IO; using System.Runtime.Serialization.Formatters.Binary; public static class CommonExtensions { ///
優點: 輕巧、簡單、速度比 BinaryFormatter 快上不少,基本上不知道要挑哪一個方式又想自己寫時,我會傾向建議使用此方式
缺點: Private 欄位無法被複製到,遇到循環參考會出錯(解決方式可參考[這篇](https://dotblogs.com.tw/wasichris/2015/12/03/152540))
建立一個擴充方法如下 ```C# using System; using Newtonsoft.Json; public static class CommonExtensions { ///
優點: 速度又比第二種方式更快,也可處理 Private 欄位、循環參考的問題
缺點: Framework 需 .NET 4 以上,我有遇到當型態是 `IEnumerable<T>` 時,會跳出錯誤(目前我是先轉 `List<T>` 型態解決),除此之外,還沒遇到其它問題
由於該擴充方法的程式碼有點冗長,所以這邊直接提供[原始碼檔案下載](https://drive.google.com/file/d/1fDE1CmIiadQLD9Ip42BWVs4_zPve28en/view?usp=sharing),然後使用方式參考下面 ```C# public class Item { public string Name { get; set; } public int Number { get; set; } public List
優點: 速度最快,假設有其他更快的方式,請告知我,感恩
缺點: 必須撰寫較多的程式碼,後續維護較麻煩且容易有人為上的疏失(假如你夠細心的話可無視)
ShallowCopy 微軟提供了內建方法`Object.MemberwiseClone()`供呼叫,和 DeepCopy 的差意主要是前者只複製實值(Value)型別的欄位,如果是參考(reference)型別的欄位,則只複製該物件欄位的參考位址,所以該欄位的值還是屬於和大家共用的狀態,至於什麼是參考型別還實值型別,由於不在本篇討論的範圍,有興趣的讀者可以自行參考這兩篇文章:[一、Value Type(實值型別) vs Reference Type(參考型別)](https://xingulin.tumblr.com/post/48493582986/ref-type-vs-val-type)、[二、實值型別與參考型別的記憶體配置](https://dotblogs.com.tw/h091237557/2014/05/26/145247) 這邊分別比較使用這兩種方式的結果 ```C# public class Item { public Item ShallowCopy() { return (Item)this.MemberwiseClone(); } public Item DeepCopy() { var other = (Item)this.MemberwiseClone(); other.Name = String.Copy(Name); other.Features = new List
優點: 速度比使用 Expression Trees 方式還快一些,懶得自己寫程式碼時,是一個還不錯的選擇,官方文件也有提供和各種方式的效能比較
缺點: 需另外安裝額外的套件,且需 .NET Framework 4 以上,不過它有支援 .Net Standard 算是一種優點吧!?
使用前需先從 Nuget 上安裝,使用方式如下 ```C# public class Item { public string Name { get; set; } public int Number { get; set; } public List
經我自己測試後,大概整理出以下的心得 效能(處理時間): 4 > 5 > 3 > 2 > 1 實用性(之後我專案傾向採用的機率來排序): 5 > 2 = 3 > 1 > 4 事實上,真的很難挑出一個十全十美的處理方式,我認為選擇出一種最適合目前手上專案的才是最重要的
##參考資料 [\[搞搞就懂\] 深層複製(DeepClone)功能實作及應用](https://dotblogs.com.tw/wasichris/2015/12/03/152540) [\[史丹利好熱\] 物件建立之淺層複製vs深層複製](https://dotblogs.com.tw/stanley14/2017/03/28/shallow_and_deep_copy) [\[Stackoverflow\] Deep cloning objects](https://stackoverflow.com/questions/78536/deep-cloning-objects) [MSDN 的 Object.MemberwiseClone Method](https://docs.microsoft.com/en-us/dotnet/api/system.object.memberwiseclone?redirectedfrom=MSDN&view=netframework-4.8#System_Object_MemberwiseClone)