關於C#中的類型
在C#中類型分為數值型別和參考類型, 參考類型和數值型別都繼承自System.Object類,幾乎所有的參考類型都直接從System.Object繼承, 而數值型別具體一點則繼承System.Object的子類, 即繼承System.ValueType。 而String類型卻有點特別, 雖然它屬於參考類型, 但是他的一些特性卻有點類似數值型別。
關於C# String1、不變性
我們先來看看一個例子:
static void Main(string[] args) { string str1 = "string"; string str2 = str1; Console.WriteLine(object.ReferenceEquals(str1, str2)); str2 += "change"; Console.WriteLine(object.ReferenceEquals(str1, str2)); Console.ReadKey; }輸出結果是True、False。 為什麼呢?我們來看看IL。
.entrypoint // 代碼大小 48 (0x30) .maxstack 2 .locals init ([0] string str1, [1] string str2) IL_0000: nop IL_0001: ldstr "string" IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: stloc.1 IL_0009: ldloc.0 IL_000a: ldloc.1 IL_000b: ceq IL_000d: call void [mscorlib]System.Console::WriteLine(bool) IL_0012: nop IL_0013: ldloc.1 IL_0014: ldstr "change" IL_0019: call string [mscorlib]System.String::Concat(string,string) IL_001e: stloc.1 IL_001f: ldloc.0 IL_0020: ldloc.1 IL_0021: ceq IL_0023: call void [mscorlib]System.Console::WriteLine(bool) IL_0028: nop IL_0029: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey IL_002e: pop IL_002f: ret+=在內部調用了Concat函數, 將str2和"change"連接起來直接生成了一個新的字串, 和原來的字串是不同的物件。 Trim、Remove函數都是會直接生成一個新的物件, 字串一經定義, 就不能改變。
其實字串具有原子性(也就是不變性),
在字串駐留池, 保存著字串字面值和指向的引用。 每次有新的字串創建, 都會在駐留池中查找是否存在字面值相同的字串, 如果存在就將其指向已經存在的字串的引用, 不存在就直接新建一個字串, 然後指向一個新的位址。
2、作為函數參數的處理
在函數的參數傳遞中,
程式先輸出Jack, 後輸出Eason, 可以說明參考類型傳遞的是引用位址, 函數改變的參數物件和外部傳遞進來的物件是一個物件。
那麼我們來看看String作為參數的情況:
static void Main(string[] args) { string str = "string"; Console.WriteLine(str); Change(str); Console.WriteLine(str); Console.ReadKey; } static void Change(string str) { str = "change"; Console.WriteLine(str); }結果輸出string、change、string。 調用Change函數後str的值還是"string", 由於字串類型的不變性, 在Change函數中對str進行賦值會重新創建一個新的字串物件, 然後為這個新的物件附上引用。 所以雖然字串類型是參考類型, 但是在參數傳遞時它其實相當於數值型別。
3、相等比較處理
先看一個例子:
string str1 = "string"; string str2 = "string"; string str3 = "stringstring"; string str4 = "string" + "string"; string str5 = str1 + "string"; Console.WriteLine(ReferenceEquals(str1, str2)); Console.WriteLine(str1 == str2); Console.WriteLine(ReferenceEquals(str3, str4)); Console.WriteLine(str3 == str4); Console.WriteLine(ReferenceEquals(str3, str5)); Console.WriteLine(str3 == str5); Console.ReadKey;不出意外結果都應該為True, True, True, True, True, True, 但是結果卻是True,
FastAllocateString函數負責分配長度為str0.Length+str1.Length的空字串dest, FillStringChecked分別將str0和str1複製到dest中, 最後生成由str0和str1連接成的字串, 這樣不會再去字串駐留池中查找是否存在和dest相同的字串, 而是直接生成一個新的物件。 所以字串變數和字串常量進行拼接後會直接生成一個新的物件, 繞過駐留池檢查。
而字串常量拼接不會產生新的字串, 除非駐留池中沒有與之拼接後字面值相等的字串。 我們來看看IL代碼:
IL_0001: ldstr "string" IL_0006: stloc.0 IL_0007: ldstr "string" IL_000c: stloc.1 IL_000d: ldstr "stringstring" IL_0012: stloc.2 IL_0013: ldstr "stringstring" IL_0018: stloc.3 IL_0019: ldloc.0 IL_001a: ldstr "string" IL_001f: call string [mscorlib]System.String::Concat(string,string) IL_0024: stloc.s str5 IL_0026: ldloc.0 IL_0027: ldloc.1str3和str4的字面值是相等的, 都是"stringstring", str3先於str4被初始化, 當str4被初始化的時候, 由於其字面值和str3相等, 所以CLR會將str3指向的位址賦給str4,
至於"=="操作符的得到的結果都是True是因為"=="操作符會調用String.Equal方法, IL代碼如下:
IL_0032: call bool [mscorlib]System.String::op_Equality(string,string)op_Equality最終會調用String.Equal函數, Equal函數的比較步驟是先比較兩個物件的引用是否相等, 不相等的話再對值進行比較, 比較值時是按位比較的。