一、String到底是值類型還是引用類型 MSDN 中明確指出 String 是引用類型而不是值類型,但 String 錶面上用起來卻像是值類型,這又是什麼原因呢? 首先從下麵這個例子入手: 輸出結果: 從運行結果可以看出:str2 的值還是 ab ,並沒有隨著 str1 值的改變而改變。如果str ...
一、String到底是值類型還是引用類型
MSDN 中明確指出 String 是引用類型而不是值類型,但 String 錶面上用起來卻像是值類型,這又是什麼原因呢?
首先從下麵這個例子入手:
//值類型 int a = 1; int b = a; a = 2; Console.WriteLine("a is {0},b is {1}", a, b); //引用類型 string str1 = "ab"; string str2 = str1; str1 = "abc"; Console.WriteLine("str1 is {0},str2 is {1}", str1, str2); Console.Read();
輸出結果:
//結果: //a is 2,b is 1 //str1 is abc,str2 is ab
從運行結果可以看出:str2 的值還是 ab ,並沒有隨著 str1 值的改變而改變。如果string是引用類型,按理Str1和Str指針都指向同一記憶體地址,如果Str的內容發生改變,Str1應該也會相應變化。此例子,看著string更像是值類型。 但是MSDN卻說String是引用類型。究其原因,是因為string對象是不可變的,包括長度和其中任何字元都是不可以改變的。
關於不可變數據類型,請參考:https://www.cnblogs.com/mushroom/p/4373951.html
String的不變性
string 對象稱為不可變的(只讀),因為一旦創建了該對象,就不能修改該對象的值。有的時候看來似乎修改了,實際是string經過了特殊處理,每次改變值時都會建立一個新的string對象,變數會指向這個新的對象,而原來的還是指向原來的對象,所以不會改變。這也是string效率低下的原因。如果經常改變string的值則應該使用StringBuilder而不使用string。
在例子中str1=”ab”,這時在記憶體中就將“ab”存下來,如果再創建字元串對象,其值也等於“ab”,str2=”ab”,則並非再重新分配記憶體空間,而是將之前保存的“ab”的地址賦給str2的引用,這就能印證例子2中的結果。而當str1=”abc”其值發生改變時,這時檢查記憶體,發現不存在此字元串,則重新分配記憶體空間,存儲“abc”,並將其地址賦給str1,而str2依然指向“ab”的地址。可以印證例子中的結果。
結論
String是引用類型,只是編譯器對其做了特殊處理。
二、淺拷貝與深拷貝
也許會有人這樣解釋C# 中淺拷貝與深拷貝區別:
淺拷貝是對引用類型拷貝地址,對值類型直接進行拷貝。
不能說它完全錯誤,但至少還不夠嚴謹。比如:string 類型咋說?
其實,我們可以通過實踐來尋找答案。
首先,定義以下類型:
int 、string 、enum 、struct 、class 、int[ ] 、string[ ]
代碼如下:
//枚舉 public enum myEnum { _1 = 1, _2 = 2 } //結構體 public struct myStruct { public int _int; public myStruct(int i) { _int = i; } } //類 class myClass { public string _string; public myClass(string s) { _string = s; } } //ICloneable:創建作為當前實例副本的新對象。 class DemoClass : ICloneable { public int _int = 1; public string _string = "1"; public myEnum _enum = myEnum._1; public myStruct _struct = new myStruct(1); public myClass _class = new myClass("1"); //數組 public int[] arrInt = new int[] { 1 }; public string[] arrString = new string[] { "1" }; //返回此實例副本的新對象 public object Clone() { //MemberwiseClone:返回當前對象的淺表副本(它是Object對象的基方法) return this.MemberwiseClone(); } }
註意:
ICloneable 介面:支持克隆,即用與現有實例相同的值創建類的新實例。
MemberwiseClone 方法:創建當前 System.Object 的淺表副本。
接下來,構建實例A ,並對實例A 克隆產生一個實例B。
然後,改變實例B 的值,並觀察實例A 的值會不會被改變。
代碼如下:
class 淺拷貝與深拷貝 { static void Main(string[] args) { DemoClass A = new DemoClass(); //創建實例A的副本 --> 新對象實例B DemoClass B = (DemoClass)A.Clone(); B._int = 2; Console.WriteLine(" int \t\t A:{0} B:{1}", A._int, B._int); B._string = "2"; Console.WriteLine(" string \t A:{0} B:{1}", A._string, B._string); B._enum = myEnum._2; Console.WriteLine(" enum \t\t A:{0} B:{1}", (int)A._enum, (int)B._enum); B._struct._int = 2; Console.WriteLine(" struct \t A:{0} B:{1}", A._struct._int, B._struct._int); B._class._string = "2"; Console.WriteLine(" class \t\t A:{0} B:{1}", A._class._string, B._class._string); B.arrInt[0] = 2; Console.WriteLine(" intArray \t A:{0} B:{1}", A.arrInt[0], B.arrInt[0]); B.arrString[0] = "2"; Console.WriteLine(" stringArray \t A:{0} B:{1}", A.arrString[0], B.arrString[0]); Console.ReadKey(); } }
結果如下:
從最後的輸出結果,我們得知:
對於內部的Class 對象和數組,則Copy 一份地址。[ 改變B 時,A也被改變了 ]
而對於其它內置的int / string / enum / struct / object 類型,則Copy 一份值。
有一位網友說:string 類型雖然是引用類型,但是很多情況下.Net 把string 做值類型來處理,我覺得string 應該也是按照值類型處理的。
這說明他對string 類型還不夠瞭解。
可以肯定的是:string 一定是引用類型。那它為什麼是深拷貝呢?
如果你看一下string 類型的源代碼就知道了:
//表示空字元串。此欄位為只讀。 public static readonly string Empty;
答案就在於 string 是 readonly 的,當改變 string 類型的數據值時,將重新分配了記憶體地址。
淺拷貝:給對象拷貝一份新的對象。
淺拷貝的定義 —— 只對值類型(或string)類型分配新的記憶體地址。
深拷貝:給對象拷貝一份全新的對象。
深拷貝的定義 —— 對值類型分配新的記憶體地址,引用類型、以及引用類型的內部欄位分配的新的地址。
我是這麼定義的:淺拷貝,換湯不換藥。
三、Clone()方法
例如我有一個簡單的類:
class People { public int _age; public string _name; public People(int Age,string Name) { _age = Age; _name = Name; } }
常見的賦值語句,如:
People Mike = new People(12,"Mike"); People Mike2 = Mike;
這是淺複製,共用同一塊記憶體,類似指針,即Mike2與Mike對象同時指向了Mike新建時所申請的記憶體。
現在我為People類增加一個Clone()方法:
class People { public int _age; public string _name; public People(int Age,string Name) { _age = Age; _name = Name; } public object Clone() { People MySelf = new People(this._age,this._name); return MySelf; } }
很明顯,調用Clone()方法返回的對象是一個全新的對象,是新實例化出來的對象但是與原對象在值上相等。
People Mike = new People(12,"Mike"); People Mike2 = Mike; People Mike3 = Mike.Clone() as People;
Mike2與Mike3在值上相等,但實際是完全獨立的對象。
Mike2._name = "Jone"; //執行上述代碼後,Mike的_name屬性跟著改變了,而Mike3不變。