今天繼續利用准備WSE安全開發文章的空閒時間,完善《.NET深入學習筆記》系列(基本都是.Net重要的知識點,我都做了詳細的總結,是什麼、為什麼、和怎麼實現)。想必很多人也接觸過這兩個概念。做過C++的人對深淺拷貝的概念一定不會陌生。而其很多C#高級軟件工程師的面試裡也會問到深淺拷貝相關的問題。我今天就在總結一下,並且添加了詳細的代碼實現,與大家分享。一起學習一下C#的深拷貝與淺拷貝(Deep Copy and Shallow Copy)的機制。全文還是分三部分:1.基本概念 2.深拷貝與淺拷貝實現機制 3.代碼實現和分析 4.總結。相面我們來進入正式的學習。
1.基本的概念:
首先我們應該了解一下什麼叫深拷貝與淺拷貝(Deep Copy and Shallow Copy)。
a.淺拷貝(Shallow Copy影子克隆):只復制對象的基本類型,對象類型,仍屬於原來的引用。
b.深拷貝(Deep Copy 深度克隆):不緊復制對象的基本類,同時也復制原對象中的對象.完全產生新對象。
我們知道,在C++中有拷貝構造函數和拷貝賦值函數的概念。淺拷貝就是成員數據之間的一一賦值:把值賦給一一賦給要拷貝的值。但是可能會有這樣的情況:對象還包含資源,這裡的資源可以值堆資源,或者一個文件。當值拷貝的時候,兩個對象就有用共同的資源,同時對資源可以訪問,這樣就會出問題。深拷貝就是用來解決這樣的問題的,它把資源也賦值一次,使對象擁有不同的資源,但資源的內容是一樣的。對於堆資源來說,就是在開辟一片堆內存,把原來的內容拷貝。
如果你拷貝的對象中引用了某個外部的內容(比如分配在堆上的數據),那麼在拷貝這個對象的時候,讓新舊兩個對象指向同一個外部的內容,就是淺拷貝;如果在拷貝這個對象的時候為新對象制作了外部對象的獨立拷貝,就是深拷貝
這個C#裡的概念與C++類似。我們可以參考以前的概念理解。 深拷貝與淺拷貝之間的區別基本可以從定義看出。首先淺拷貝是指將對象中的數值類型的字段拷貝到新的對象中,而對象中的引用型字段則指復制它的一個引用到目標對象。如果改變目標對象中引用型字段的值他將反映在原是對象中,也就是說原始對象中對應的字段也會發生變化。
深拷貝與淺拷貝不同的是對於引用拷貝的處理,深拷貝將會在新對象中創建和原是對象中對應值類型的字段並且賦值。淺拷貝不會創建新引用類型,會返回相同的類型引用。深拷貝會重新創建新對象,返回新對象的引用字。C#重的觀察者模式就是淺拷貝的例子。我們保留的只是對象的副本。
2.深拷貝與淺拷貝實現機制:
從上面的概念我們了解了C#深拷貝與淺拷貝(Deep Copy and Shallow Copy)的不同之處。這個也就決定了兩者有不同的實現方式。
對於值類型:
a.淺拷貝:通過賦值等操作直接實現,將對象中的值類型的字段拷貝到新的對象中。
b.深拷貝:通過賦值等操作直接實現,將對象中的值類型的字段拷貝到新的對象中。和淺拷貝相同
對於引用類型:
a.值類型:MemberwiseClone 方法創建一個淺副本,方法是創建一個新對象,如果字段是值類型的,則對該字段執行逐位復制。如果字段是引用類型,則復制引用原始對象,與原對象引用同一對象。
b.引用類型:拷貝對象應用,也拷貝對象實際內容,也就是創建了一個新的改變新對象 不會影響到原始對象的內容
這種情況需要為其實現ICloneable接口中提供的Clone方法。
差別就是在對於引用類型的實現深拷貝和淺拷貝的時候的機制不同,前者是MemberwiseClone 方法實現,後者是通過繼承實現ICloneable接口中提供的Clone方法,實現對象的深入拷貝。
3.代碼實現和分析:
下面我們來看看具體的代碼實現部分,首先介紹的還是值類型的。
a.值類型淺拷貝的實現。代碼如下:
/// <summary>
/// 數組的=賦值(直接拷貝),也就是引用傳遞,指向的是同一個地址:
/// </summary>
public void MethodShallowCopyDirectly()
{
int[] ArrayInt = { 0, 1, 2, 3 };
//所以改變其中任意一個變量的值,另一個也會被改變
int[] NewArrayInt = ArrayInt;
//改變新的數組變量:
NewArrayInt[0] = 8;
Console.WriteLine("數組的復制(直接拷貝),改變新數組第一值為8,原值{0},新值{1}", ArrayInt[0], NewArrayInt[0]);
}
/// <summary>
/// ArrayInt.CopyTo,創建以個新數組,不影響原來的值
/// </summary>
public void MethodShallowCopyArrayCopyTo()
{
int[] ArrayInt = { 0, 1, 2, 3 };
//CopyTo()方法
int[] NewArrayInt = new int[5];//創建以個新數組,按值拷貝,不影響原來的值
ArrayInt.CopyTo(NewArrayInt, 0);
NewArrayInt[0] = 8;
Console.WriteLine("Array.CopyTo,改變新數組第一值為8,原值{0},新值{1}", ArrayInt[0], NewArrayInt[0]);
}
/// <summary>
/// Array.Copy淺拷貝,值類型的淺拷貝,創建以個新數組,按值拷貝,不影響原來的值
/// </summary>
public void MethodShallowCopyArrayCopy()
{
int[] ArrayInt = { 0, 1, 2, 3 };
//Copy()方法
int[] NewArrayInt = new int[4];
Array.Copy(ArrayInt, NewArrayInt, 0);//創建以個新數組,按值拷貝,不影響原來的值
NewArrayInt[0] = 8;
Console.WriteLine("Array.Copy,改變新數組第一值為8,原值{0},新值{1}", ArrayInt[0], NewArrayInt[0]);
}
/// <summary>
/// Array.Clone(),淺拷貝
/// </summary>
public void MethodShallowCopyArrayClone()
{
int[] ArrayInt = { 0, 1, 2, 3 };
//Array Clone()方法
int[] NewArrayInt = ArrayInt.Clone() as int[];//按值拷貝,不影響原來的值
NewArrayInt[0] = 8;
Console.WriteLine("Array.Clone(),改變新數組第一值為8,原值{0},新值{1}", ArrayInt[0], NewArrayInt[0]);
}
/// <summary>
/// .淺拷貝:(引用類型),數組中的元素是引用類型,復制的是它的一個引用,改變新拷貝會改變原對象
/// </summary>
public void MethodShallowCopyStringArrayCopyTo()
{
string[] sArray ={ "string0", "string1", "string2" };
string[] sNewArray = sArray;
//淺拷貝一個新對象
sArray.CopyTo(sNewArray, 0);
//改變新對象的值這個時候源對象中的值也會被改變
sNewArray[0] = "FrankXuLei";
Console.WriteLine("數組的淺拷貝:(引用類型),改變第一值為FrankXuLei,原值{0},新值{1}", sArray[0], sNewArray[0]);
}
/// <summary>
/// //字符串數組的深拷貝,如果需要包含引用類型的數組的深副本,就必須迭代數組,創建新對象
/// </summary>
public void MethodDeepCopyStringArray()
{
string[] sArray = new string[] { "string0", "string1", "string2", "string3" };
string[] sNewArray = new string[4];//迭代數組,創建新對象
for (int i = 0; i < sArray.Length; i++)
{
string sTemp = string.Empty;
sTemp = sArray[i];
sNewArray[i] = sTemp;
}
sNewArray[0] = "FrankXuLei";
Console.WriteLine("數組的復制(直接拷貝),改變新數組第一值為FrankXuLei,原值{0},新值{1}", sArray[0], sNewArray[0]);
}
所以改變其中任意一個變量的值,另一個也會被改變。ArrayInt.CopyTo,創建以個新數組,改變新的數組變量不影響原來的值。Array.Copy淺拷貝,值類型的淺拷貝,創建以個新數組,按值拷貝,不影響原來的值。 /// .淺拷貝:(引用類型),數組中的元素是引用類型,復制的是它的一個引用,改變新拷貝會改變原對象.
b.引用類型的深拷貝實現:
定義了以個汽車類,繼承接口繼承接口ICloneable。
public class CarDeepClone : ICloneable
{
//名稱,引用類型
public string _name = string.Empty;
//價格,值得類型
public int _price = 0;
//構造函數
public CarDeepClone()
{
}
//重載構造函數
public CarDeepClone(string name, int price)
{
_name = name;
_price = price;
}
//深拷貝,需要重新生成對象,返回的新對象的實例
public object Clone()
{
//深復制
CarDeepClone obj = new CarDeepClone();//重新創建 CarDeepClone的對象
//obj.Member= (ClassA)Member.Clone();
return obj;
}
}
c.引用類型的淺拷貝實現:
淺拷貝實現的方法是this.MemberwiseClone();創建當前對象的淺副本 ,返回相同的對象引用。而深拷貝的實現代碼是通過 CarDeepClone obj = new CarDeepClone();重新創建 CarDeepClone的對象。這個是兩者在實現上不同的地方。
public class CarShallowClone : ICloneable
{
//名稱,引用類型
public string _name = string.Empty;
//價格,值得類型
public int _price = 0;
//構造函數
public CarShallowClone(string name, int price)
{
_name = name;
_price = price;
}
//淺拷貝,MemberwiseClone方式返回對象的淺副本
public object Clone()
{
return this.MemberwiseClone();//創建當前對象的淺副本 ,返回相同的對象引用
}
}
d.客戶端測試代碼實現:
包括值類型的淺拷貝和string類型數組的深拷貝的實現測試。以及對象的深拷貝和淺拷貝的測試。具體代碼如下:
ValueTypeCopy _ShallowCopy = new ValueTypeCopy();
Console.WriteLine("Value Type Shallow Copy Demo 值類型淺拷貝。。。。。。。。。。。。。。。。。。");
_ShallowCopy.MethodShallowCopyDirectly();//直接賦值
_ShallowCopy.MethodShallowCopyArrayClone();//調用數組的Clone()方法,淺副本
_ShallowCopy.MethodShallowCopyArrayCopy();//ArrayCopy方法
_ShallowCopy.MethodShallowCopyArrayCopyTo();//ArrayCopyTo()方法
_ShallowCopy.MethodShallowCopyStringArrayCopyTo();//ArrayCopyTo()方法
_ShallowCopy.MethodDeepCopyStringArray();//深拷貝字符數組
//DeepCopy Test深拷貝,重新生成對象,對新對象的修改不會改變原來對象的值
Console.WriteLine("Object Deep Copy Demo 對象深拷貝。。。。。。。。。。。。。。。。。。。。。");
CarDeepClone _CarDeepClone1 = new CarDeepClone("Benz700",700);
//深拷貝
Console.WriteLine("DeepCopy Test深拷貝,原對象名字{0}", _CarDeepClone1._name);
CarDeepClone _CarDeepClone2 = _CarDeepClone1.Clone() as CarDeepClone;
Console.WriteLine("DeepCopy Test深拷貝,新對象名字{0}", _CarDeepClone2._name);
//修改新對象的名字
_CarDeepClone2._name = "Benz800";
Console.WriteLine("DeepCopy Test深拷貝,新對象名字修改為{0}", _CarDeepClone2._name);
//輸出對象信息
Console.WriteLine("DeepCopy Test深拷貝,原對象名字為{0},新對象名字為{1}", _CarDeepClone1._name, _CarDeepClone2._name);
//ShallowCopy Test淺拷貝,新對象的修改會改變原來對象的值得
Console.WriteLine("Object Shallow Copy Demo 對象淺拷貝。。。。。。。。。。。。。。。。。。。。。");
CarShallowClone _CarShallowClone1 = new CarShallowClone("BMW3", 300);
Console.WriteLine("ShallowCopy Test淺拷貝,原對象名字{0}", _CarShallowClone1._name);
//淺拷貝對象
CarShallowClone _CarShallowClone2 = _CarShallowClone1.Clone() as CarShallowClone;
Console.WriteLine("ShallowCopy Test淺拷貝,新對象名字{0}", _CarShallowClone2._name);
//修改新對象名字
_CarShallowClone2._name = "BMW7";
Console.WriteLine("ShallowCopy Test淺拷貝,新對象名字修改為{0}", _CarShallowClone2._name);
//輸出對象信息
Console.WriteLine("ShallowCopy Test淺拷貝,原對象名字{0},新對象名字{1}", _CarShallowClone1._name, _CarShallowClone2._name);
首先測試的值類型的不同的淺拷貝方法,實例化類ValueTypeCopy _ShallowCopy = new ValueTypeCopy();
進行 值類型淺拷測試貝。分別包括:
_ShallowCopy.MethodShallowCopyDirectly();直接賦值拷貝,
_ShallowCopy.MethodShallowCopyArrayClone();調用數組的Clone()方法,淺副本
_ShallowCopy.MethodShallowCopyArrayCopy();ArrayCopy方法
_ShallowCopy.MethodShallowCopyArrayCopyTo();ArrayCopyTo()方法
_ShallowCopy.MethodShallowCopyStringArrayCopyTo();ArrayCopyTo()方法
_ShallowCopy.MethodDeepCopyStringArray();深拷貝字符數組後面代碼主要是對對象深淺拷貝的不同測試。
運行結果如下圖:
4.總結
以上的內容的學習,希望大家對C#的深拷貝與淺拷貝(Deep Copy and Shallow Copy)的機制能有一個深入的了解。我在總結這個文章的時候也查閱了MSDN及C#書籍等資料。希望能與大家一起分享。
本文配套源碼