C# 由範式編程==運算符引起對string內存分配的思考

今天在看C#編程指南時(類型參數的約束http://msdn.microsoft.com/zh-cn/library/d5x73970.aspx)看到一段描述:html

在應用 where T : class 約束時,避免對類型參數使用 ==!= 運算符,由於這些運算符僅測試引用同一性而不測試值相等性。即便在用做參數的類型中重載這些運算符也是如此。下面的代碼說明了這一點;即便 String 類重載 == 運算符,輸出也爲 false。 編程

並給出的代碼測試

public static void OpTest<T>(T s, T t) where T : class
{
    System.Console.WriteLine(s == t);
}
static void Main()
{
    string s1 = "target";
    System.Text.StringBuilder sb = new System.Text.StringBuilder("target");
    string s2 = sb.ToString();
    OpTest<string>(s1, s2);
}

返回的結果爲False即s1!=s2
到這裏都十分容易理解,由於在C#中==運算符對於值類型,若是對象的值相等,則相等運算符 (==) 返回 true,不然返回 false。對於引用類型,若是兩個對象引用同一個對象,則 == 返回 true。ui

因此若是我將代碼改成:this

static void Main()
{
  string s1 = "target";
  System.Text.StringBuilder sb
= new System.Text.StringBuilder("target");
  string s2 = sb.ToString();
  s2
= s1;
  OpTest
<string>(s1, s2); }

返回的結果爲TRUE即s1==s2,由於s1和s2引用同一個對象。spa

接下來我又作了一個實驗(重點來了):設計

static void Main()
{  
    string s1 = "target";  
    string s2 = "target";  
    OpTest<string>(s1, s2);
}

返回的結果依然是TRUE即s1==s2,這個結果徹底出乎個人預料!思考中......3d

==這裏其實有一個很大的陷阱!上面的理解有一處很大的破綻!由於筆者忘記了很重要的一條!code

在C#參考中有如下描述(== 運算符(C# 參考)http://127.0.0.1:47873/help/1-30636/ms.help?product=VS&productVersion=100&method=f1&query=%3D%3D_CSharpKeyword%00%3D%3D&locale=zh-CN&category=DevLang%3acsharp%00TargetFrameworkMoniker%3a.NETFramework,Version%3Dv4.0htm

對於預約義的值類型,若是操做數的值相等,則相等運算符 ( ==) 返回 true,不然返回 false。 對於 string 之外的引用類型,若是兩個操做數引用同一個對象,則 == 返回 true。 對於 string 類型, == 比較字符串的值。

也就是說string類型其實有對==運算符進行重載(實時上做者在剛開始閱讀C#編程指南時明顯理解錯誤),實時如此:

string s1 = "target";
System.Text.StringBuilder sb = new System.Text.StringBuilder("target");
string s2 = sb.ToString();

if (s1 == s2)
{
  System.Console.WriteLine(
"=="); } else {
  System.Console.WriteLine(
"!="); }

結果顯示s1 == s2 返回True,而在本文開頭提出的C#編程指南時(類型參數的約束)例子中講的就是雖然string有對==運算符進行重載,可是在範式編程中會被無視,因此要慎用(好吧,筆者的基本功堪憂啊!),那麼爲啥

string s1 = "target";  
string s2 = "target";  
OpTest<string>(s1, s2);

會獲得s1==s2的結果?

讓咱們換個思考方式:

1.在OpTest<string>(s1, s2);中==判斷的是兩個操做數是否引用的同一個對象,若是是則返回True,反之False(包含string類型)

2.在上述例子中s1==s2,則證實s1和s2引用的是同一個對象

因而筆者又嘗試了

string[] str = new string[3];
str[0] = "123";
str[1] = "123";
str[2] = "123";

OpTest<string>(str[0], str[2]);
string str0 = @"D:\mysher";
string str1 = "D:\\mysher";
OpTest<string>(str0, str1);

等到的結果都是兩個string變量引用了同一個對象
因此筆者獲得結論當有多個字符串變量包含了一樣的字符串實際值時,CLR讓它們向同一個字符串對象。

那麼既然是這樣,Microsoft在C#編程中給出的例子

string s1 = "target";
System.Text.StringBuilder sb = new System.Text.StringBuilder("target");
string s2 = sb.ToString();
OpTest<string>(s1, s2);

s1和s2也應該知足條件,指向同一個對象啊,可事實卻相反。

後來筆者在園子裏找到了答案,感謝cyoooo7給出了十分詳細的解釋:

CLR默默地維護了一個叫作駐留池(Intern Pool)的表。這個表記錄了全部在代碼中使用字面量聲明的字符串實例的引用。這說明使用字面量聲明的字符串會進入駐留池,而其餘方式聲明的字符串並不會進入,也就不會自動享受到CLR防止字符串冗餘的機制的好處了。可是在不使用字面量聲明時,如CLR在爲ToString()方法的返回值分配內存時,並不會到駐留池中去檢查是否字符串已經存在了,因此會從新分配內存。

爲了讓編程者可以強制CLR檢查駐留池,以免冗餘的字符串副本,String類的設計者提供了一個名爲Intern的類方法,以下

string s1 = "target";
System.Text.StringBuilder sb = new System.Text.StringBuilder("target");
string s2 = String.Intern(sb.ToString());
OpTest<string>(s1, s2);

這樣s1和s2又指向同一個對象了。
若是有興趣的朋友能夠去看看cyoooo7《原來是這樣:C#中字符串的內存分配與駐留池》一文。

相關文章
相關標籤/搜索