2012-01-18 31 views
13

tôi đã có một suy nghĩ trước khi so sánh hai chuỗi với các biến số của họ:So sánh chuỗi == chỉ hoạt động vì chuỗi không thay đổi?

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); 

Đây có phải là hoàn toàn là vì thời gian chạy .NET nhận giá trị của chuỗi là giống nhau và vì dây là không thay đổi làm cho các tài liệu tham khảo của str2 tương đương với của str1?

Vì vậy, khi chúng ta làm str1 == str2 chúng tôi đang thực sự tài liệu tham khảo so sánh và không phải là giá trị? Ban đầu tôi nghĩ đây là sản phẩm của đường cú pháp, nhưng tôi đã sai?

Bất kỳ sự không chính xác nào với những gì tôi đã viết?

Trả lời

7

Nếu chúng ta nhìn vào mã được trích, chúng ta sẽ thấy rằng str2 được lắp ráp bằng cách sử dụng String.Concat và thực tế không phải là tham chiếu giống như str1. Chúng tôi cũng sẽ thấy rằng so sánh được thực hiện bằng cách sử dụng Equals. Nói cách khác, khẳng định đi qua khi các chuỗi chứa các ký tự giống nhau.

Mã này

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

được JITted đến (vui lòng cuộn ngang để xem ý kiến)

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

Trên thực tế, String.Equals kiểm tra đầu tiên nếu đó là cùng một tham chiếu và nếu không so sánh nội dung.

+3

@EricJ. Nhưng nếu chúng có cùng một địa chỉ bộ nhớ thì ** ** phải có nội dung giống nhau (sau cùng là * cùng *). – Yuck

+0

@Yuck - Chỉ khi thực tập là một phần của thông số kỹ thuật và không chỉ là chi tiết triển khai. Ngoài ra, các chuỗi trong tên miền ứng dụng riêng biệt có thể bằng nhau và có địa chỉ khác nhau. – psr

+0

@psr Đúng, đó là lý do tại sao kiểm tra có điều kiện. Nếu tham chiếu là như nhau thì bạn đã hoàn thành - đó là nó. Nếu không, bạn phải so sánh nội dung của mỗi biến để xác định sự bình đẳng logic. – Yuck

14

Câu trả lời là trong C# Spec §7.10.7

Chuỗi nhà khai thác bình đẳng so sánh các giá trị chuỗi chứ không phải là tài liệu tham khảo chuỗi . Khi hai cá thể chuỗi riêng biệt chứa cùng một chuỗi ký tự giống nhau , giá trị của các chuỗi bằng nhau, nhưng các tham chiếu thì khác nhau. Như được mô tả trong §7.10.6, loại tham chiếu các toán tử bình đẳng có thể được sử dụng để so sánh các tham chiếu chuỗi thay vì giá trị chuỗi .

+2

Không áp dụng khi so sánh cùng chuỗi xâu - được thực thi dưới dạng hằng số thời gian biên dịch. – TomTom

+0

@TomTom điểm tốt. – DaveShaw

+1

@TomTom Áp dụng vì khái niệm đó là những gì nó làm, mặc dù nó vẫn sẽ nhấn phím tắt tham chiếu bình đẳng trước khi nó được như xa như so sánh giá trị. –

2

Đây có phải là hoàn toàn là vì thời gian chạy .NET nhận giá trị của chuỗi là giống nhau và vì dây là không thay đổi làm cho các tài liệu tham khảo của str2 bằng của str1?

No. FIrst, đó là vì str1 và str2 giống hệt nhau - chúng có cùng chuỗi do trình biên dịch có thể tối ưu hóa. strFoo + strFoo là một hằng số biên dịch thời gian itendical đến str1. Khi các chuỗi được INTERNED trong các lớp, chúng sử dụng cùng một chuỗi.

Thứ hai, chuỗi OVERRIDES phương pháp ==. Kiểm tra mã nguồn từ các nguồn tham khảo có sẵn trên internet trong một thời gian.

+4

Nếu bạn chạy đoạn mã trên và kiểm tra 'object.ReferenceEquals (str1, str2);', bạn sẽ nhận được 'false'. Đó là một phân tâm để đề cập đến interning khi nó không áp dụng cho kịch bản hiện tại. Phần thứ hai của bạn, tất nhiên, hoàn toàn hợp lệ. –

+3

'string' * overloads * toán tử' == '. Bạn không thể ghi đè * toán tử trong C# vì chúng luôn là 'tĩnh'. – dan04

10

No.

== hoạt động vì lớp String quá tải toán tử == tương đương với phương thức Bằng.

Từ Reflector:

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

Các bình đẳng tham khảo điều hành == thể được ghi đè; và trong trường hợp System.String, nó được ghi đè để sử dụng hành vi bình đẳng giá trị. Đối với sự tham chiếu bình đẳng thực, bạn có thể sử dụng phương thức Object.ReferenceEquals(), không thể ghi đè.

0

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

Đối với các loại giá trị được xác định trước, các nhà điều hành bình đẳng (==) trả về true nếu giá trị của toán hạng của nó đều bình đẳng, false. Đối với các kiểu tham chiếu không phải là chuỗi, == trả về true nếu hai toán hạng của nó tham chiếu đến cùng một đối tượng.Đối với loại chuỗi, == so sánh giá trị của chuỗi.

1

Theo thứ tự mã của bạn truy cập ...

== bị ghi đè. Điều này có nghĩa là thay vì "abc" == "ab" + "c", hãy gọi số == mặc định cho các loại tham chiếu (so sánh tham chiếu chứ không phải giá trị) mà nó gọi vào string.Equals(a, b).

Bây giờ, điều này như sau:

  1. Nếu hai thực sự là tài liệu tham khảo cùng, trở thành sự thật.
  2. Nếu một trong hai là null, trả về false (chúng tôi đã trả về true ở trên nếu cả hai đều là null).
  3. nếu hai chiều dài khác nhau, trả về false;
  4. Thực hiện chu trình tối ưu hóa thông qua một chuỗi, so sánh nó với char còn lại với phần còn lại (int-for-int được xem như hai khối int trong bộ nhớ, là một trong những tối ưu liên quan). Nếu nó đạt đến kết thúc mà không có sự không khớp, sau đó trả về true, ngược lại trả về false.

Nói cách khác, nó bắt đầu với cái gì đó như:

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; 
} 

Trừ bước 4 là một phiên bản dựa trên con trỏ với một vòng lặp unrolled rằng nên vì thế lý tưởng được nhanh hơn. Tôi sẽ không thể hiện điều đó bởi vì tôi muốn nói về logic tổng thể.

Có các vết cắt ngắn đáng kể. Đầu tiên là ở bước 1. Vì sự bình đẳng là phản xạ (danh tính đòi hỏi sự bình đẳng, a == a) thì chúng ta có thể trả về true trong nano giây cho dù chỉ một vài chuỗi MB, nếu so sánh với chính nó.

Bước 2 không phải là cắt ngắn, vì điều kiện phải được kiểm tra, nhưng lưu ý rằng vì chúng tôi đã trả lại giá trị đúng cho (string)null == (string)null, chúng tôi không cần một chi nhánh khác. Vì vậy, thứ tự của cuộc gọi được hướng đến một kết quả nhanh chóng.

Bước 3 cho phép hai điều. Cả hai đều cắt ngắn trên các chuỗi có độ dài khác nhau (luôn luôn sai) và có nghĩa là người ta không thể vô tình bắn qua cuối của một chuỗi được so sánh trong bước 4.

Lưu ý rằng đây không phải là trường hợp so sánh chuỗi khác , kể từ ví dụ WEISSBIERweißbier có độ dài khác nhau nhưng cùng một từ viết hoa khác nhau, vì vậy so sánh phân biệt chữ hoa chữ thường không thể sử dụng bước 3. Tất cả so sánh bình đẳng có thể thực hiện bước 1 và 2 như quy tắc được sử dụng luôn giữ, vì vậy bạn nên sử dụng chúng theo cách riêng của mình một số có thể thực hiện bước 3.

Do đó, mặc dù bạn sai khi cho rằng đó là tham chiếu chứ không phải là giá trị được so sánh, đúng là tham chiếu được so sánh đầu tiên dưới dạng cắt ngắn rất đáng kể. Cũng lưu ý rằng các chuỗi nội bộ (các chuỗi được đặt trong hồ bơi thực tập bằng cách biên dịch hoặc bằng cách gọi là string.Intern được gọi) sẽ do đó kích hoạt cắt ngắn này thường xuyên. Đây sẽ là trường hợp trong đoạn mã trong ví dụ của bạn, vì trình biên dịch sẽ sử dụng cùng một tham chiếu trong mỗi trường hợp.

Nếu bạn biết rằng một chuỗi được tập trung, bạn có thể phụ thuộc vào điều này (chỉ cần làm kiểm tra bình đẳng tham khảo), nhưng ngay cả khi bạn không biết chắc chắn bạn có thể hưởng lợi từ nó (tham khảo kiểm tra bình đẳng sẽ cắt ngắn ít nhất đôi lúc).

Nếu bạn có một chuỗi các chuỗi nơi bạn muốn thử nghiệm một số trong số chúng thường xuyên, nhưng bạn không muốn kéo dài tuổi thọ của chúng trong bộ nhớ nhiều như thực tập, thì bạn có thể sử dụng XmlNameTable hoặc LockFreeAtomizer (sắp được đổi tên thành ThreadSafeAtomizer và tài liệu được chuyển thành http://hackcraft.github.com/Ariadne/documentation/html/T_Ariadne_ThreadSafeAtomizer_1.htm - lẽ ra phải được đặt tên cho hàm thay vì chi tiết triển khai ngay từ đầu).

Giá trị cũ được sử dụng trong nội bộ XmlTextReader và do đó có rất nhiều phần còn lại của System.Xml và có thể được sử dụng bởi mã khác. Sau này tôi đã viết vì tôi muốn có một ý tưởng tương tự, đó là an toàn cho các cuộc gọi đồng thời, cho các loại khác nhau, và nơi tôi có thể ghi đè so sánh bình đẳng.

Trong cả hai trường hợp, nếu bạn đặt 50 chuỗi khác nhau là tất cả "abc" vào nó, bạn sẽ nhận được một tham chiếu "abc" duy nhất cho phép những người khác được thu gom rác. Nếu bạn biết điều này đã xảy ra, bạn có thể phụ thuộc vào ReferenceEquals một mình và nếu bạn không chắc chắn, bạn sẽ vẫn được hưởng lợi từ việc cắt ngắn khi xảy ra sự cố.