Sự khác biệt giữa == và Equals () đối với các nguyên hàm trong C # là gì?


180

Xem xét mã này:

int age = 25;
short newAge = 25;
Console.WriteLine(age == newAge);  //true
Console.WriteLine(newAge.Equals(age)); //false
Console.ReadLine();

Cả hai intshortlà loại nguyên thủy, nhưng so sánh ==trả về true và so sánh với Equalstrả về false.

Tại sao?


9
@OrangeDog Vui lòng suy nghĩ về câu hỏi và sau đó bỏ phiếu để đóng

4
Điều này đang thiếu nỗ lực đảo ngược rõ ràng:Console.WriteLine(age.Equals(newAge));
ANeves

3
Bản sao không giải thích hành vi này; nó chỉ là về những gì Equals()nói chung.
SLaks

37
Tôi đã trả lời chính xác câu hỏi này trên blog Coverity vài ngày trước. blog.coverity.com/2014/01/13/inconsistent-equality
Eric Lippert

5
@CodesInChaos: Thông số kỹ thuật thực sự sử dụng thuật ngữ "các kiểu nguyên thủy" hai lần mà không bao giờ xác định nó; hàm ý là các kiểu nguyên thủy là các kiểu giá trị tích hợp, nhưng điều này không bao giờ được làm rõ. Tôi đã đề nghị với Mads rằng thuật ngữ này chỉ đơn giản là được lấy từ đặc tả vì nó dường như tạo ra nhiều nhầm lẫn hơn là loại bỏ.
Eric Lippert

Câu trả lời:


262

Câu trả lời ngắn:

Bình đẳng là phức tạp.

Câu trả lời chi tiết:

Các kiểu nguyên thủy ghi đè lên cơ sở object.Equals(object)và trả về true nếu hộp objectđược đóng cùng loại và giá trị. (Lưu ý rằng nó cũng sẽ hoạt động đối với các loại nullable; các loại nullable null null luôn đóng hộp cho một thể hiện của loại cơ bản.)

newAgelà một short, Equals(object)phương thức của nó chỉ trả về true nếu bạn vượt qua một ô ngắn có cùng giá trị. Bạn đang vượt qua một hộp int, vì vậy nó trả về sai.

Ngược lại, ==toán tử được định nghĩa là lấy hai ints (hoặc shorts hoặc longs).
Khi bạn gọi nó bằng một intvà a short, trình biên dịch sẽ ngầm chuyển đổi shortthành intvà so sánh các ints kết quả theo giá trị.

Những cách khác để làm cho nó hoạt động

Các loại nguyên thủy cũng có riêng của họ Equals() phương thức chấp nhận cùng loại.
Nếu bạn viết age.Equals(newAge), trình biên dịch sẽ chọn int.Equals(int)là quá tải tốt nhất và hoàn toàn chuyển đổi shortthành int. Sau đó nó sẽ trả về true, vì phương thức này chỉ đơn giản là so sánh inttrực tiếp s.

shortcũng có một short.Equals(short)phương thức, nhưng intkhông thể được chuyển đổi hoàn toàn thành short, vì vậy bạn không gọi nó.

Bạn có thể buộc nó gọi phương thức này bằng cách sử dụng:

Console.WriteLine(newAge.Equals((short)age)); // true

Điều này sẽ gọi short.Equals(short)trực tiếp, không có quyền anh. Nếu agelớn hơn 32767, nó sẽ ném ngoại lệ tràn.

Bạn cũng có thể gọi short.Equals(object) quá tải, nhưng rõ ràng chuyển một đối tượng được đóng hộp để nó có cùng loại:

Console.WriteLine(newAge.Equals((object)(short)age)); // true

Giống như sự thay thế trước đó, điều này sẽ tạo ra một tràn nếu nó không phù hợp với một short. Không giống như giải pháp trước đó, nó sẽ đóng hộp shortvào một đối tượng, gây lãng phí thời gian và bộ nhớ.

Mã nguồn:

Dưới đây là cả hai Equals()phương pháp từ mã nguồn thực tế:

    public override bool Equals(Object obj) {
        if (!(obj is Int16)) {
            return false;
        }
        return m_value == ((Int16)obj).m_value;
    }

    public bool Equals(Int16 obj)
    {
        return m_value == obj;
    }

Đọc thêm:

Xem Eric Lippert .


3
@SLaks, nếu chúng ta gọi long == int, intngầm chuyển thành longphải không?
Selman Genç

1
Và vâng, tôi đã viết tất cả lên mà không thực sự thử nó.
SLaks

1
Hãy nhớ rằng, trong đoạn code của câu hỏi, nếu một trong những thay đổi int age = 25;để const int age = 25;, sau đó kết quả sẽ thay đổi. Đó là bởi vì một chuyển đổi ngầm từ intđể shortkhông tồn tại trong trường hợp đó. Xem ẩn chuyển đổi biểu thức hằng .
Jeppe Stig Nielsen

2
@SLaks có nhưng từ ngữ của câu trả lời của bạn "giá trị được thông qua" có thể được hiểu theo cả hai cách (như giá trị được chuyển qua bởi nhà phát triển hoặc giá trị thực sự được CLR chuyển qua sau khi bỏ hộp). Tôi đoán người dùng bình thường chưa biết câu trả lời ở đây sẽ đọc nó như trước đây
JaredPar

2
@Rachel: Ngoại trừ điều đó không đúng; các mặc định == hành so sánh các loại tài liệu tham khảo bằng cách tham khảo. Đối với các loại giá trị và đối với các loại quá tải ==, thì không.
SLaks

55

Bởi vì không có quá tải cho short.Equalsviệc chấp nhận một int. Do đó, điều này được gọi là:

public override bool Equals(object obj)
{
    return obj is short && this == (short)obj;
}

objkhông phải là một short.. do đó, nó là sai.


12

Khi bạn vượt qua intđến shortBằng của bạn, bạn vượt qua object:

nhập mô tả hình ảnh ở đây Vì vậy, mã giả này chạy:

return obj is short && this == (short)obj;


10

==được sử dụng để kiểm tra một điều kiện bằng nhau, nó có thể được coi là một toán tử (toán tử boolean), chỉ để so sánh 2 điều và ở đây kiểu dữ liệu không quan trọng vì sẽ có một kiểu truyền được thực hiện và Equalscũng được sử dụng để kiểm tra điều kiện bằng , nhưng trong trường hợp này các kiểu dữ liệu phải giống nhau. N Equals là một phương thức không phải là toán tử.

Dưới đây là một ví dụ nhỏ được lấy từ một trong những bạn đã cung cấp và điều này sẽ làm rõ sự khác biệt ngắn gọn ,.

int x=1;
short y=1;
x==y;//true
y.Equals(x);//false

trong ví dụ trên, X và Y có cùng các giá trị tức là 1 và khi chúng ta sử dụng ==, nó sẽ trả về true, như trong trường hợp ==, loại ngắn được chuyển đổi thành int bởi trình biên dịch và kết quả được đưa ra.

và khi chúng ta sử dụng Equals, việc so sánh được thực hiện, nhưng việc truyền kiểu không được thực hiện bởi trình biên dịch, do đó false được trả về.

Các bạn, xin vui lòng cho tôi biết nếu tôi sai.


6

Trong nhiều bối cảnh trong đó một đối số phương thức hoặc toán tử không thuộc loại được yêu cầu, trình biên dịch C # sẽ cố gắng thực hiện chuyển đổi kiểu ẩn. Nếu trình biên dịch có thể làm cho tất cả các đối số thỏa mãn toán tử và phương thức của chúng bằng cách thêm các chuyển đổi ngầm định, nó sẽ làm như vậy mà không có khiếu nại, mặc dù trong một số trường hợp (đặc biệt là với các bài kiểm tra đẳng thức!), Kết quả có thể gây ngạc nhiên.

Hơn nữa, mỗi loại giá trị như inthoặc shortthực sự mô tả cả một loại giá trị và một loại đối tượng (*). Chuyển đổi ngầm định tồn tại để chuyển đổi giá trị sang các loại giá trị khác và để chuyển đổi bất kỳ loại giá trị nào sang loại đối tượng tương ứng, nhưng các loại đối tượng khác nhau không thể chuyển đổi lẫn nhau.

Nếu một người sử dụng == toán tử để so sánh a shortvà an int, thì shortnó sẽ được chuyển đổi hoàn toàn thành một int. Nếu giá trị số của nó là tương đương với các int, các intmà nó đã được chuyển đổi sẽ bằng intmà nó được so sánh. Tuy nhiên, nếu người ta cố gắng sử dụng Equalsphương thức ngắn để so sánh nó với một phương thức int, thì chuyển đổi ngầm định duy nhất đáp ứng quá tải của Equalsphương thức sẽ là chuyển đổi sang loại đối tượng tương ứng int. Khi shortđược hỏi liệu nó có khớp với đối tượng truyền vào hay không, nó sẽ quan sát rằng đối tượng trong câu hỏi là một intchứ không phải là một shortvà do đó kết luận rằng nó không thể bằng nhau.

Nói chung, mặc dù trình biên dịch sẽ không phàn nàn về nó, nhưng ta nên tránh so sánh những thứ không cùng loại; nếu một người quan tâm đến việc chuyển đổi mọi thứ sang một hình thức phổ biến sẽ cho kết quả tương tự, thì người ta nên thực hiện chuyển đổi đó một cách rõ ràng. Hãy xem xét, ví dụ,

int i = 16777217;
float f = 16777216.0f;

Console.WriteLine("{0}", i==f);

Có ba cách mà người ta có thể muốn so sánh intvới a float. Người ta có thể muốn biết:

  1. Liệu floatgiá trị gần nhất có thể intphù hợp với float?
  2. Có phải toàn bộ phần số của floatkhớp với int?
  3. Làm intfloatđại diện cho cùng một giá trị số.

Nếu một người cố gắng so sánh một intfloattrực tiếp, mã được biên dịch sẽ trả lời câu hỏi đầu tiên; cho dù đó là những gì lập trình viên dự định, tuy nhiên, sẽ không rõ ràng. Thay đổi so sánh để (float)i == flàm rõ rằng ý nghĩa đầu tiên được dự định, hoặc(double)i == (double)f sẽ khiến mã trả lời câu hỏi thứ ba (và làm rõ nó là ý định gì).

(*) Ngay cả khi C # đặc tả coi một giá trị kiểu ví dụ System.Int32như là một đối tượng kiểu System.Int32, một cái nhìn như vậy là mâu thuẫn với yêu cầu rằng một mã chạy trên một nền tảng có liên quan giá trị và các đối tượng như sinh sống vũ trụ khác nhau spec. Hơn nữa, nếu Tlà một loại tham chiếu, và xlà một T, thì một tham chiếu của loại Tsẽ có thể tham chiếu x. Vì vậy, nếu một biến vloại và nội dung của nó thực sự là một .Int32 giữ một Object, tham chiếu kiểu Objectsẽ có thể giữ tham chiếu đến vhoặc nội dung của nó. Trong thực tế, một tham chiếu loại Objectsẽ có thể trỏ đến một đối tượng đang giữ dữ liệu được sao chép từ đóv , nhưng không phải cho vchính nó cũng như nội dung của nó. Điều đó sẽ gợi ý rằng khôngvObject


1
the only implicit conversion which would satisfy an overload of the Equals method would be the conversion to the object type corresponding to intSai lầm. Không giống như Java, C # không có các kiểu nguyên thủy và đóng hộp riêng biệt. Nó được đóng hộp objectvì đó là sự quá tải duy nhất khác Equals().
SLaks

Câu hỏi thứ nhất và thứ ba là giống hệt nhau; giá trị chính xác đã bị mất khi chuyển đổi thành float. Đúc một floatđến một doublesẽ không kỳ diệu tạo chính xác mới.
SLaks

@SLaks: Theo thông số ECMA, mô tả máy ảo mà C # chạy, mỗi định nghĩa loại giá trị tạo ra hai loại khác nhau. Thông số kỹ thuật C # có thể nói rằng nội dung của vị trí lưu trữ loại List<String>.Enumeratorvà đối tượng heap loại List<String>.Enumeratorgiống nhau, nhưng thông số ECMA / CLI cho biết chúng khác nhau và ngay cả khi được sử dụng trong C #, chúng hoạt động khác nhau.
supercat

@SLaks: Nếu iftừng được chuyển đổi thành doubletrước khi so sánh, chúng sẽ mang lại 16777217.0 và 16777216.0, so sánh là không bằng nhau. Chuyển đổi i floatsẽ mang lại 16777216.0f, so sánh bằng f.
supercat

@SLaks: Để biết ví dụ đơn giản về sự khác biệt giữa các loại vị trí lưu trữ và các loại đối tượng được đóng hộp, hãy xem xét phương thức bool SelfSame<T>(T p) { return Object.ReferenceEquals((Object)p,(Object)p);}. Loại đối tượng được đóng hộp tương ứng với một loại giá trị có thể đáp ứng loại tham số ReferenceEqualsthông qua nhận dạng bảo toàn upcast; tuy nhiên, loại vị trí lưu trữ yêu cầu chuyển đổi không bảo toàn danh tính . Nếu chọn một Tđể Umang lại một tham chiếu đến một cái gì đó khác với bản gốc T, điều đó sẽ gợi ý cho tôi rằng Tkhông thực sự là a U.
supercat

5

Equals () là một phương thức của System.Object Class
Syntax: Public virtual bool Equals ()
Khuyến nghị nếu chúng ta muốn so sánh trạng thái của hai đối tượng thì chúng ta nên sử dụng phương thức Equals ()

như đã nêu ở trên câu trả lời == toán tử so sánh các giá trị là như nhau.

Xin đừng nhầm lẫn với ReferenceEqual

Reference Equals ()
Cú pháp: public static bool ReferenceEquals ()
Nó xác định xem các đối tượng được chỉ định có thuộc cùng một thể hiện không


8
Điều này không trả lời câu hỏi nào cả.
SLaks

SLaks tôi không giải thích với các ví dụ này là cơ bản của câu hỏi trên.
Sugat Mankar

4

Những gì bạn cần nhận ra là làm ==sẽ luôn luôn gọi một phương thức. Câu hỏi là liệu gọi ==Equalskết thúc cuộc gọi / làm những điều tương tự.

Với các loại tham chiếu, ==sẽ luôn kiểm tra thứ nhất xem các tham chiếu có giống nhau không ( Object.ReferenceEquals). Equalsmặt khác có thể được ghi đè và có thể kiểm tra xem một số giá trị có bằng nhau không.

EDIT: để trả lời svick và thêm vào bình luận SLaks, đây là một số mã IL

int i1 = 0x22; // ldc.i4.s ie pushes an int32 on the stack
int i2 = 0x33; // ldc.i4.s 
short s1 = 0x11; // ldc.i4.s (same as for int32)
short s2 = 0x22; // ldc.i4.s 

s1 == i1 // ceq
i1 == s1 // ceq
i1 == i2 // ceq
s1 == s2 // ceq
// no difference between int and short for those 4 cases,
// anyway the shorts are pushed as integers.

i1.Equals(i2) // calls System.Int32.Equals
s1.Equals(s2) // calls System.Int16.Equals
i1.Equals(s1) // calls System.Int32.Equals: s1 is considered as an integer
// - again it was pushed as such on the stack)
s1.Equals(i1) // boxes the int32 then calls System.Int16.Equals
// - int16 has 2 Equals methods: one for in16 and one for Object.
// Casting an int32 into an int16 is not safe, so the Object overload
// must be used instead.

Vậy phương pháp nào so sánh hai ints với == gọi? Gợi ý: không có operator ==phương pháp cho Int32, nhưng có mộtString phương pháp cho .
Svick

2
Điều này không trả lời câu hỏi nào cả.
SLaks

@SLaks: nó thực sự không trả lời câu hỏi cụ thể về so sánh int và ngắn, bạn đã trả lời nó. Tôi vẫn cảm thấy thật thú vị khi giải thích rằng ==không chỉ làm phép thuật, cuối cùng nó chỉ đơn giản gọi một phương thức (hầu hết các lập trình viên có thể không bao giờ thực hiện / ghi đè bất kỳ toán tử nào). Có lẽ tôi có thể đã thêm một bình luận cho câu hỏi của bạn thay vì thêm câu trả lời của riêng tôi. Hãy cập nhật thông tin của bạn nếu bạn cảm thấy những gì tôi nói là có liên quan.
dùng276648

Lưu ý rằng ==trên các kiểu nguyên thủy không phải là toán tử quá tải, mà là một tính năng ngôn ngữ nội tại sẽ biên dịch theo lệnh ceqIL.
SLaks

3

== Nguyên thủy

Console.WriteLine(age == newAge);          // true

Trong so sánh nguyên thủy == toán tử hoạt động khá rõ ràng, Trong C # có rất nhiều == toán tử quá tải có sẵn.

  • chuỗi == chuỗi
  • int == int
  • uint == uint
  • dài == dài
  • nhiều hơn nữa

Vì vậy, trong trường hợp này không có chuyển đổi ngầm từ intđến shortnhưng shortđể intlà có thể. Vì vậy, newAge được chuyển đổi thành int và so sánh xảy ra trả về giá trị true vì cả hai đều giữ cùng một giá trị. Vì vậy, nó tương đương với:

Console.WriteLine(age == (int)newAge);          // true

.Equals () trong Nguyên thủy

Console.WriteLine(newAge.Equals(age));         //false

Ở đây chúng ta cần xem phương thức Equals () là gì, chúng ta gọi Equals bằng một biến loại ngắn. Vì vậy, có ba khả năng:

  • Bằng (đối tượng, đối tượng) // phương thức tĩnh từ đối tượng
  • Bằng (đối tượng) // phương thức ảo từ đối tượng
  • Bằng (ngắn) // Triển khai IEquitable.Equals (ngắn)

Loại đầu tiên không phải là trường hợp ở đây vì số lượng đối số là khác nhau, chúng tôi gọi chỉ với một đối số của loại int. Thứ ba cũng được loại bỏ như đã đề cập ở trên chuyển đổi ngầm định của int thành ngắn là không thể. Vì vậy, ở đây Loại thứ hai Equals(object)được gọi là. Các short.Equals(object)là:

bool Equals(object z)
{
  return z is short && (short)z == this;
}

Vì vậy, ở đây điều kiện đã được kiểm tra z is shortlà sai vì z là một int nên nó trả về false.

Dưới đây là bài viết chi tiết từ Eric Lippert

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.