2012-01-18 82 views
13

我曾與他們的變量比較兩個字符串時,前一個念頭:字符串比較==只工作,因爲字符串是不可變的?

string str1 = "foofoo"; 
string strFoo = "foo"; 
string str2 = strFoo + strFoo; 

// Even thought str1 and str2 reference 2 different 
//objects the following assertion is true. 

Debug.Assert(str1 == str2); 

這純粹是因爲.NET運行時識別字符串的值是相同的,因爲字符串是不可變使得str2等於參考str1

所以,當我們做str1 == str2我們實際上比較引用和不是值?我原本以爲這是語法糖的產物,但是我錯了嗎?

我寫的東西有什麼不準確?

回答

7

如果我們看看jitted代碼,我們將看到str2使用String.Concat進行組裝,並且它實際上與str1不同。我們也會看到使用Equals完成了比較。換句話說,斷言傳遞,因爲字符串包含相同的字符。

此代碼

static void Main(string[] args) 
{ 
    string str1 = "foofoo"; 
    string strFoo = "foo"; 
    string str2 = strFoo + strFoo; 
    Console.WriteLine(str1 == str2); 
    Debugger.Break(); 
} 

被實時編譯到(請橫向滾動到評論)

C:\dev\sandbox\cs-console\Program.cs @ 22: 
00340070 55    push ebp 
00340071 8bec   mov  ebp,esp 
00340073 56    push esi 
00340074 8b3530206003 mov  esi,dword ptr ds:[3602030h] ("foofoo") <-- Note address of "foofoo" 

C:\dev\sandbox\cs-console\Program.cs @ 23: 
0034007a 8b0d34206003 mov  ecx,dword ptr ds:[3602034h] ("foo") <-- Note different address for "foo" 

C:\dev\sandbox\cs-console\Program.cs @ 24: 
00340080 8bd1   mov  edx,ecx 
00340082 e81977fe6c  call mscorlib_ni+0x2b77a0 (6d3277a0)  (System.String.Concat(System.String, System.String), mdToken: 0600035f) <-- Call String.Concat to assemble str2 
00340087 8bd0   mov  edx,eax 
00340089 8bce   mov  ecx,esi 
0034008b e870ebfd6c  call mscorlib_ni+0x2aec00 (6d31ec00)  (System.String.Equals(System.String, System.String), mdToken: 060002d2) <-- Compare using String.Equals 
00340090 0fb6f0   movzx esi,al 
00340093 e83870f86c  call mscorlib_ni+0x2570d0 (6d2c70d0) (System.Console.get_Out(), mdToken: 060008fd) 
00340098 8bc8   mov  ecx,eax 
0034009a 8bd6   mov  edx,esi 
0034009c 8b01   mov  eax,dword ptr [ecx] 
0034009e 8b4038   mov  eax,dword ptr [eax+38h] 
003400a1 ff5010   call dword ptr [eax+10h] 

C:\dev\sandbox\cs-console\Program.cs @ 28: 
003400a4 e87775596d  call mscorlib_ni+0x867620 (6d8d7620) (System.Diagnostics.Debugger.Break(), mdToken: 0600239a) 

C:\dev\sandbox\cs-console\Program.cs @ 29: 
>>> 003400a9 5e    pop  esi 
003400aa 5d    pop  ebp 
003400ab c3    ret 
7

其實,String.Equals首先檢查它是否是相同的參考,如果沒有比較內容。

+3

@EricJ。但是如果他們確實有相同的內存地址,那麼**必須**具有相同的內容(畢竟它是*相同*實例)。 – Yuck 2012-01-18 17:06:32

+0

@Yuck - 只有實習是規範的一部分,而不僅僅是實現細節。此外,單獨的應用程序域中的字符串可能是相同的並且具有不同的地址。 – psr 2012-01-18 19:04:50

+0

@psr對,這就是爲什麼有條件檢查。如果引用是相同的,那麼你就完成了 - 就是這樣。否則,您必須比較每個變量的內容以確定邏輯相等。 – Yuck 2012-01-18 19:09:07

14

答案是在C#規格§7.10.7

字符串相等運算符比較字符串值,而不是字符串 引用。當兩個單獨的字符串實例包含完全相同的字符序列時,字符串的值相等,但 引用是不同的。如§7.10.6所述,參考類型 等於運算符可用於比較字符串引用而不是 字符串值。

+2

不適用,因爲他比較了相同的字符串trice - 作爲編譯時間常量被實現。 – TomTom 2012-01-18 17:01:48

+0

@TomTom好點。 – DaveShaw 2012-01-18 17:02:58

+1

@TomTom適用,因爲它在概念上就是它的作用,儘管在比較值之前它仍會按照引用等同的快捷方式。 – 2012-01-18 18:20:47

2

這純粹是因爲.NET運行時識別字符串的值是相同的,因爲 字符串是不可變使得STR2的等於str1中引用?

不,首先,這是因爲str1和str2是相同的 - 它們是相同的字符串,因爲編譯器可以優化它。 strFoo + strFoo是對str1的編譯時常量。由於字符串是INTERNED類,它們使用相同的字符串。

二,string OVERRIDES tthe ==方法。查看互聯網上的參考資源的源代碼一段時間。

+4

如果你要運行上面的代碼片段並檢查'object.ReferenceEquals(str1,str2);',你應該得到'false'。如果不適用於當前情況,提及實習可能會讓人分心。你的第二部分當然是完全有效的。 – 2012-01-18 18:02:39

+3

'string' *重載*'=='運算符。你不能*覆蓋* C#中的一個運算符,因爲它們總是「靜態」的。 – dan04 2012-01-18 19:17:48

10

==工作,因爲String類重載==操作符等同於Equals方法。

從反射器:

[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] 
public static bool operator ==(string a, string b) 
{ 
    return Equals(a, b); 
} 
2

基準相等運算==可以被覆蓋;而在System.String的情況下,它被覆蓋使用值相等行爲。對於真正的參考平等,您可以使用Object.ReferenceEquals()方法,該方法不能被覆蓋。

0

按照MSDN(http://msdn.microsoft.com/en-us/library/53k8ybth.aspx):

對於預定義的值的類型,如果它的操作數的值相等,否則爲假相等運算符(==)返回true。對於字符串以外的引用類型,如果其兩個操作數引用同一對象,則==會返回true。對於字符串類型,==比較字符串的值。

1

在你的代碼打它...的順序

==被覆蓋。這意味着調用缺省==而不是"abc" == "ab" + "c"作爲引用類型(它比較引用而不是值),它調用string.Equals(a, b)。現在

,這個執行以下操作:

  1. 如果兩者確實是相同的,返回true。
  2. 如果其中任何一個都爲null,則返回false(如果它們都爲null,則返回true)。
  3. 如果兩者長度不同,則返回false;
  4. 通過一個字符串進行優化循環,將char-for-char與其餘字符進行比較(實際上int-for-int被視爲內存中的兩個整型塊,這是所涉及的優化之一)。如果達到最終結果時不失配,則返回true,否則返回false。

換句話說,它與類似的東西啓動:

public static bool ==(string x, string y) 
{ 
    //step 1: 
    if(ReferenceEquals(x, y)) 
    return true; 
    //step 2: 
    if(ReferenceEquals(x, null) || ReferenceEquals(y, null)) 
    return false; 
    //step 3; 
    int len = x.Length; 
    if(len != y.Length) 
    return false; 
    //step 4: 
    for(int i = 0; i != len; ++i) 
    if(x[i] != y[i]) 
     return false; 
    return true; 
} 

除步驟4是一個基於指針的版本與展開循環應該因此理想地更快。我不會表明這一點,因爲我想談論整體邏輯。

有很大的捷徑。第一個是在步驟1中。因爲平等是自反的(身份需要相等,a == a),那麼如果與自身相比,我們甚至可以在幾納米大小的情況下返回真實的納秒數。

步驟2不是一個捷徑,因爲它的一個條件必須進行測試,但請注意,因爲我們已經返回了真實的(string)null == (string)null我們不需要另一個分支。所以調用的順序是快速的結果。

第3步允許兩件事。它既可以對不同長度的字符串進行快捷方式切換(總是爲假),也可以意味着在步驟4中不會意外拍攝超過一個字符串的末尾。

請注意,其他字符串比較不是這種情況,因爲例如WEISSBIERweißbier的長度不同,但是大小寫不同,因此不區分大小寫的比較不能使用第3步。所有相等比較都可以執行第1步和第2步,因爲所使用的規則總是保持不變,所以您應該只使用它們有些人可以做第3步。

因此,雖然你錯誤地暗示它是引用而不是比較值,但確實將引用首先作爲非常重要的捷徑進行比較。還要注意,實習字符串(通過編譯放置在實習生池中的字符串或稱爲string.Intern)將因此經常觸發此快捷方式。在您的示例代碼中就是這種情況,因爲編譯器在每種情況下都會使用相同的引用。

如果你知道一個字符串被攔截,你可以依賴這個(只是做引用相等性測試),但即使你不知道你可以從中受益(引用相等性測試將會至少縮短一些時間)。

如果你有一堆字符串,你會想經常測試它們中的某些字符串,但是你不想像內部一樣在內存中延長它們的壽命,那麼你可以使用XmlNameTable或者LockFreeAtomizer(很快將重命名爲ThreadSafeAtomizer,並且文檔已移至http://hackcraft.github.com/Ariadne/documentation/html/T_Ariadne_ThreadSafeAtomizer_1.htm--應該首先命名爲函數而不是實現細節)。

前者由XmlTextReader在內部使用,因此也由System.Xml的其餘部分使用,也可以被其他代碼使用。我寫的後者是因爲我想要一個類似的想法,這對於併發調用是安全的,對於不同類型,以及我可以重寫相等比較的位置。

在任何一種情況下,如果您放入50個全部爲「abc」的不同字符串,您將得到一個單獨的「abc」引用,允許其他垃圾收集。如果您知道發生了這種情況,您可以單獨依靠ReferenceEquals,如果您不確定,那麼您仍然可以從捷徑中獲益。