Tại sao (đối tượng) 0 == (đối tượng) 0 khác với ((đối tượng) 0) .Equals ((đối tượng) 0)?


117

Tại sao các biểu thức sau lại khác nhau?

[1]  (object)0 == (object)0 //false
[2]  ((object)0).Equals((object)0) // true

Trên thực tế, tôi hoàn toàn có thể hiểu [1] bởi vì có thể thời gian chạy .NET sẽ boxlà số nguyên và thay vào đó bắt đầu so sánh các tham chiếu. Nhưng tại sao [2] lại khác?


36
Được rồi, bây giờ bạn đã hiểu câu trả lời cho câu hỏi này, hãy kiểm tra mức độ hiểu biết của bạn bằng cách dự đoán kết quả của: short myShort = 0; int myInt = 0; Console.WriteLine("{0}{1}{2}", myShort.Equals(myInt), myInt.Equals(myShort), myInt == myShort); Bây giờ hãy kiểm tra nó với thực tế. Dự đoán của bạn có đúng không? Nếu không, bạn có thể giải thích sự khác biệt?
Eric Lippert 17/12/13

1
@Star, để đọc được khuyến nghị, hãy xem msdn.microsoft.com/en-us/library/vstudio/… để biết tình trạng quá tải có sẵn trên phương pháp int16aka shortEquals, sau đó xem msdn.microsoft.com/en-us/library/ms173105.aspx . Tôi không muốn làm hỏng câu đố của Eric Lippert, nhưng sẽ khá dễ dàng để tìm ra khi bạn đọc những trang đó.
Sam Skuce

2
Tôi nghĩ đây là một câu hỏi java; ít nhất là trước khi nhìn thấy chữ 'E' trong Dấu bằng.
seteropere 17/12/13

4
@seteropere Java thực sự khác: autoboxing trong Java lưu trữ các đối tượng, do đó, ((Integer)0)==((Integer)0)đánh giá là true.
Jules

1
Bạn cũng có thể thử IFormattable x = 0; bool test = (object)x == (object)x;. Không có quyền mới nào được thực hiện khi cấu trúc đã ở trong một hộp.
Jeppe Stig Nielsen

Câu trả lời:


151

Lý do các cuộc gọi hoạt động khác nhau là chúng liên kết với các phương thức rất khác nhau.

Các ==trường hợp sẽ liên kết với các nhà điều hành bình đẳng tham khảo tĩnh. Có 2 intgiá trị đóng hộp độc lập được tạo do đó chúng không cùng tham chiếu.

Trong trường hợp thứ hai, bạn liên kết với phương thức instance Object.Equals. Đây là một phương thức ảo sẽ lọc xuống Int32.Equalsvà điều này sẽ kiểm tra một số nguyên được đóng hộp. Cả hai giá trị nguyên là 0 do đó chúng bằng nhau


Các ==trường hợp không gọi Object.ReferenceEquals. Nó chỉ đơn giản tạo ra lệnh ceqIL để thực hiện so sánh tham chiếu.
Sam Harwell

8
@ 280Z28 đó không phải chỉ vì trình biên dịch nội tuyến nó?
markmnl

@ 280Z28 Vậy? Một trường hợp tương tự là phương thức Boolean.ToString của họ dường như chứa các chuỗi được mã hóa cứng bên trong hàm của nó, thay vì trả về Boolean.TrueString và Boolean.FalseString được công khai. Nó không liên quan; Vấn đề là, ==làm điều tương tự như ReferenceEquals(trên Object, dù sao). Tất cả chỉ là tối ưu hóa nội bộ về phía MS để tránh các lệnh gọi hàm nội bộ không cần thiết trên các hàm không được sử dụng.
Nyerguds

6
Đặc tả ngôn ngữ C #, đoạn 7.10.6, cho biết: Các toán tử bình đẳng kiểu tham chiếu được xác định trước là: bool operator ==(object x, object y); bool operator !=(object x, object y);Các toán tử trả về kết quả của việc so sánh hai tham chiếu là bằng nhau hoặc không bằng nhau. Nó không phải là một yêu cầu rằng phương pháp System.Object.ReferenceEqualsđược sử dụng để xác định kết quả. Đối với @markmnl: Không, trình biên dịch C # không nội dòng, đó là điều mà đôi khi jitter vẫn làm (nhưng không phải trong trường hợp này). Vì vậy, 280Z28 là đúng ReferenceEqualsphương pháp không được sử dụng thực sự.
Jeppe Stig Nielsen

@JaredPar: Thật thú vị khi thông số kỹ thuật nói lên điều đó, vì đó không phải là cách ngôn ngữ thực sự hoạt động. Với các nhà khai thác được xác định như trên, và các biến Cat Whiskers; Dog Fido; IDog Fred;(đối với các giao diện không liên quan ICatIDogvà các lớp học không có liên quan Cat:ICatDog:IDog), so sánh Whiskers==FidoWhiskers==34sẽ là hợp pháp (là người đầu tiên chỉ có thể là đúng nếu Whiskers and Fido đều null; thứ hai không bao giờ có thể thành sự thật ). Trên thực tế, trình biên dịch C # sẽ từ chối cả hai. Whiskers==Fred;sẽ bị cấm nếu Catbị niêm phong, nhưng được phép nếu không.
supercat

26

Khi bạn truyền giá trị int 0(hoặc bất kỳ kiểu giá trị nào khác) sang object, giá trị sẽ được đóng hộp . Mỗi ép kiểu để objecttạo ra một hộp khác nhau (tức là một thể hiện đối tượng khác nhau). Nhà ==điều hành choobject Thực hiện kiểu so sánh tài liệu tham khảo, vì vậy nó trả về false từ phía bên trái tay và bên phải không phải là một ví dụ.

Mặt khác, khi bạn sử dụng Equals, là một phương thức ảo, nó sử dụng việc triển khai kiểu hộp thực tế, tức là Int32.Equals, trả về true vì cả hai đối tượng có cùng giá trị.


18

Các ==nhà điều hành, là tĩnh, không phải là ảo. Nó sẽ chạy mã chính xác màobject lớp xác định (`đối tượng là kiểu thời gian biên dịch của các toán hạng), sẽ thực hiện so sánh tham chiếu, bất kể kiểu thời gian chạy của một trong hai đối tượng.

Các Equalsphương pháp là một phương pháp dụ ảo. Nó sẽ chạy mã được xác định trong kiểu thời gian chạy thực tế của đối tượng (đầu tiên), không phải mã trong objectlớp. Trong trường hợp này, đối tượng là một int, vì vậy nó sẽ thực hiện một phép so sánh giá trị, vì đó là intkiểu xác định cho Equalsphương thức của nó .


==thông báo thực sự đại diện cho hai toán tử, một trong số đó có thể quá tải và một trong số đó không. Hành vi của toán tử thứ hai rất khác với hành vi của quá tải trên (đối tượng, đối tượng).
supercat

13

Các Equals()phương pháp là ảo.
Do đó, nó luôn gọi việc triển khai cụ thể, ngay cả khi vùng gọi được truyền đến object. intghi đè Equals()để so sánh theo giá trị, vì vậy bạn nhận được so sánh giá trị.


10

== Sử dụng: Object.ReferenceEquals

Object.Equals so sánh giá trị.

Các object.ReferenceEquals phương pháp so sánh tài liệu tham khảo. Khi bạn cấp phát một đối tượng, bạn nhận được một tham chiếu có chứa một giá trị cho biết vị trí bộ nhớ của nó cùng với dữ liệu của đối tượng trên heap bộ nhớ.

Các object.Equalsphương pháp so sánh nội dung của đối tượng. Trước tiên, nó kiểm tra xem các tham chiếu có bằng nhau hay không, đối tượng cũng vậy. Nhưng sau đó nó gọi các phương thức Equals dẫn xuất để kiểm tra sự bình đẳng hơn nữa. Xem cái này:

   System.Object a = new System.Object();
System.Object b = a;
System.Object.ReferenceEquals(a, b);  //returns true

Mặc dù Object.ReferenceEqualshoạt động giống như một phương thức sử dụng ==toán tử C # trên các toán hạng của nó, nhưng toán tử bình đẳng tham chiếu C # (được biểu diễn bằng cách sử dụng ==trên các loại toán hạng không có quá tải được định nghĩa) sử dụng một lệnh đặc biệt thay vì gọi ReferenceEquals. Hơn nữa, Object.ReferenceEqualssẽ chấp nhận các toán hạng chỉ có thể khớp nếu cả hai đều là null và sẽ chấp nhận các toán hạng cần phải được cưỡng chế kiểu Objectvà do đó không thể khớp với bất kỳ thứ gì, trong khi phiên bản bình đẳng tham chiếu của ==sẽ từ chối biên dịch cách sử dụng đó .
supercat

9

Toán tử C # sử dụng mã thông báo ==để đại diện cho hai toán tử khác nhau: một toán tử so sánh tĩnh có thể quá tải và một toán tử so sánh tham chiếu không quá tải. Khi nó gặp ==mã thông báo, trước tiên nó sẽ kiểm tra xem có tồn tại bất kỳ quá tải kiểm tra bình đẳng nào áp dụng cho các loại toán hạng hay không. Nếu vậy, nó sẽ gọi quá tải đó. Nếu không, nó sẽ kiểm tra xem các loại có áp dụng được cho toán tử so sánh tham chiếu hay không. Nếu vậy, nó sẽ sử dụng toán tử đó. Nếu không có toán tử nào áp dụng được cho các kiểu toán hạng, thì quá trình biên dịch sẽ không thành công.

Mã này (Object)0không chỉ đơn thuần là bị ném lên trời một Int32đến Object: Int32, giống như tất cả các loại giá trị, thực sự đại diện cho hai loại, một trong số đó mô tả các giá trị và các địa điểm lưu trữ (chẳng hạn như số không theo nghĩa đen), nhưng không xuất phát từ bất cứ điều gì, và một trong số đó mô tả đống đối tượng và dẫn xuất từ Object; bởi vì chỉ loại thứ hai mới có thể được upcast Object, trình biên dịch phải tạo một đối tượng heap mới của loại thứ hai đó. Mỗi lệnh gọi (Object)0tạo ra một đối tượng heap mới, vì vậy hai toán hạng để== là các đối tượng khác nhau, mỗi trong số đó, độc lập, đóng gói Int32giá trị 0.

Lớp Objectkhông có bất kỳ quá tải có thể sử dụng nào được xác định cho toán tử bằng. Do đó, trình biên dịch sẽ không thể sử dụng toán tử kiểm tra bình đẳng đã được nạp chồng và sẽ quay lại sử dụng kiểm tra bình đẳng tham chiếu. Bởi vì hai toán hạng để ==tham chiếu đến các đối tượng khác nhau, nó sẽ báo cáo false. So sánh thứ hai thành công vì nó hỏi một cá thể heap-object Int32xem nó có bằng với đối tượng kia hay không. Vì cá thể đó biết ý nghĩa của việc bằng với một cá thể riêng biệt khác nên nó có thể trả lời true.


Ngoài ra, mỗi khi bạn viết một ký tự 0trong mã của mình, tôi giả sử nó tạo một đối tượng int trong heap cho điều đó. Nó không phải là một tham chiếu duy nhất để một giá trị tĩnh không toàn cầu (giống như cách họ đã làm String.Empty để tránh làm mới chuỗi rỗng đối tượng chỉ cho khởi tạo chuỗi mới) Vì vậy, tôi khá chắc chắn rằng ngay cả làm một 0.ReferenceEquals(0)sẽ trả về false, vì cả hai 0s là Int32các đối tượng mới được tạo .
Nyerguds

1
@Nyerguds, tôi khá chắc chắn rằng mọi thứ bạn nói là không chính xác, về ints, heap, history, global statics, v.v. 0.ReferenceEquals(0)sẽ không thành công vì bạn đang cố gọi một phương thức trên hằng số thời gian biên dịch. không có đối tượng để treo nó đi. Một int unboxed là một cấu trúc, được lưu trữ trên ngăn xếp. Thậm chí int i = 0; i.ReferenceEquals(...)sẽ không hoạt động. Bởi vìSystem.Int32 KHÔNG kế thừa từ Object.
Andrew Backer

@AndrewBacker, System.Int32là a struct, a structSystem.ValueType, bản thân nó kế thừa System.Object. Tại sao có một ToString()phương pháp và mộtEquals phương phápSystem.Int32
Sebastian

1
Tuy nhiên, Nyerguds đã sai khi tuyên bố rằng Int32 sẽ được tạo trên heap, điều này không đúng như vậy.
Sebastian

@SebastianGodelet, tôi hơi phớt lờ nội bộ trong đó. System.Int32 tự thực hiện các phương thức đó. GetType () đã xuất hiện Objectvà đó là nơi tôi đã ngừng lo lắng về nó từ lâu. Nó không bao giờ cần thiết để đi xa hơn. AFAIK CLR xử lý hai loại khác nhau và đặc biệt. Nó không chỉ là sự kế thừa. Nóis là một trong hai loại dữ liệu. Tôi chỉ không muốn bất cứ ai đọc nhận xét đó và đi quá xa, bao gồm cả sự kỳ lạ về chuỗi rỗng bỏ qua việc xen vào chuỗi.
Andrew Backer

3

Cả hai séc đều khác nhau. Cái đầu tiên kiểm tra danh tính , cái thứ hai kiểm tra sự bình đẳng . Nói chung, hai thuật ngữ giống hệt nhau, nếu chúng đề cập đến cùng một đối tượng. Điều này ngụ ý rằng chúng bình đẳng. Hai số hạng bằng nhau, nếu giá trị của chúng bằng nhau.

Về nhận dạng lập trình thường bị rối bởi bình đẳng tham chiếu. Nếu con trỏ đến cả hai số hạng đều bằng nhau (!), Thì đối tượng mà chúng đang trỏ đến là chính xác cùng một đối tượng. Tuy nhiên, nếu các con trỏ khác nhau, giá trị của các đối tượng mà chúng đang trỏ đến vẫn có thể bằng nhau. Trong C #, danh tính có thể được kiểm tra bằng cách sử dụng Object.ReferenceEqualsthành viên tĩnh , trong khi bình đẳng được kiểm tra bằng cách sử dụng Object.Equalsthành viên không tĩnh . Vì bạn đang đúc hai số nguyên với các đối tượng (được gọi là "đấm bốc", btw), các operatior ==của objectthực hiện việc kiểm tra đầu tiên, đó là theo mặc định ánh xạ tới Object.ReferenceEqualsvà kiểm tra danh tính. Nếu bạn gọi một cách rõ ràng là Equals-member không tĩnh , điều phối động dẫn đến một cuộc gọi đếnInt32.Equals , điều này sẽ kiểm tra sự bình đẳng.

Cả hai khái niệm đều tương tự, nhưng không giống nhau. Thoạt đầu chúng có vẻ khó hiểu, nhưng sự khác biệt nhỏ là rất quan trọng! Hãy tưởng tượng hai người, đó là "Alice" và "Bob". Cả hai đều đang sống trong một ngôi nhà màu vàng. Dựa trên giả định, Alice và Bob đang sống trong một quận, nơi các ngôi nhà chỉ khác nhau về màu sắc của chúng, cả hai có thể sống trong những ngôi nhà màu vàng khác nhau. Nếu bạn so sánh cả hai ngôi nhà, bạn sẽ nhận ra rằng chúng hoàn toàn giống nhau, bởi vì chúng đều có màu vàng! Tuy nhiên, họ không ở chung một nhà và do đó các ngôi nhà của họ bình đẳng , nhưng không giống hệt nhau . Nhận dạng có nghĩa là họ đang sống trong cùng một ngôi nhà.

Lưu ý : một số ngôn ngữ đang xác định ===toán tử để kiểm tra danh tính.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.