關於C 中的類型 在C 中類型分為值類型和引用類型,引用類型和值類型都繼承自System.Object類,幾乎所有的引用類型都直接從System.Object繼承,而值類型具體一點則繼承System.Object的子類,即繼承System.ValueType。而String類型卻有點特別,雖然它屬於 ...
關於C#中的類型
在C#中類型分為值類型和引用類型,引用類型和值類型都繼承自System.Object類,幾乎所有的引用類型都直接從System.Object繼承,而值類型具體一點則繼承System.Object的子類,即繼承System.ValueType。而String類型卻有點特別,雖然它屬於引用類型,但是他的一些特性卻有點類似值類型。
關於C# String
1、不變性
我們先來看看一個例子:
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函數都是會直接生成一個新的對象,字元串一經定義,就不能改變。
其實字元串具有原子性(也就是不變性),任何改變字元串的值的行為都不會成功,只會創建一個新的字元串對象。在實際編程中,我們會大量的使用字元串,這樣就會導致不停地創建新的字元串對象和分配記憶體,可能導致垃圾回收器GC不停地進行垃圾回收,大大降低性能,並且伴隨著記憶體溢出的危險。所以.Net對字元串進行了的特殊的處理,這就是字元串駐留池。
在字元串駐留池,保存著字元串字面值和指向的引用。每次有新的字元串創建,都會在駐留池中查找是否存在字面值相同的字元串,如果存在就將其指向已經存在的字元串的引用,不存在就直接新建一個字元串,然後指向一個新的地址。
2、作為函數參數的處理
在函數的參數傳遞中,值類型直接拷貝變數保存的值,傳遞的是一個值得副本,而引用類型傳遞的是地址的一個副本,所以在函數中改變引用參數中屬性的值會直接改變函數外真實類型對象的值。
static void Main(string[] args)
{
People people = new People() { Name = "Jack" };
Console.WriteLine(people.Name);
Change(people);
Console.WriteLine(people.Name);
Console.ReadKey();
}
static void Change(People p)
{
p.Name = "Eason";
}
class People
{
public string Name { get; set; }
}
程式先輸出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,True,True,True,False,True,str3和str5不是一個對象,他們不是指向同一個地址,為什麼呢?經過查看IL代碼發現,str5在IL代碼中調用了Concat函數將str1和"string"進行了拼接,那這個Concat函數到底做了什麼。
public static string Concat(string str0, string str1)
{
if (IsNullOrEmpty(str0))
{
if (IsNullOrEmpty(str1))
{
return Empty;
}
return str1;
}
if (IsNullOrEmpty(str1))
{
return str0;
}
int length = str0.Length;
string dest = FastAllocateString(length + str1.Length);
FillStringChecked(dest, 0, str0);
FillStringChecked(dest, length, str1);
return dest;
}
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.1
str3和str4的字面值是相等的,都是"stringstring",str3先於str4被初始化,當str4被初始化的時候,由於其字面值和str3相等,所以CLR會將str3指向的地址賦給str4,所以str3和str4引用是相等的。
至於"=="操作符的得到的結果都是True是因為"=="操作符會調用String.Equal方法,IL代碼如下:
IL_0032: call bool [mscorlib]System.String::op_Equality(string,string)
op_Equality最終會調用String.Equal函數,Equal函數的比較步驟是先比較兩個對象的引用是否相等,不相等的話再對值進行比較,比較值時是按位比較的。