Toán tử C # .Equals (), .ReferenceEquals () và ==


84

Sự hiểu biết của tôi về ba điều này là:

  • .Equals()kiểm tra sự bình đẳng dữ liệu (thiếu mô tả tốt hơn). .Equals()có thể trả về True cho các trường hợp khác nhau của cùng một đối tượng và đây là phương thức được ghi đè phổ biến nhất.

  • .ReferenceEquals() kiểm tra xem hai đối tượng có giống nhau hay không và không thể bị ghi đè.

  • ==giống như ReferenceEquals()theo mặc định, nhưng điều này CÓ THỂ được ghi đè.

Nhưng trạm C # cho biết:

Trong lớp đối tượng, các EqualsReferenceEqualsphương thức tương đương nhau về mặt ngữ nghĩa, ngoại trừ việc các phương thức này ReferenceEqualschỉ hoạt động trên các cá thể đối tượng. Các ReferenceEqualsphương pháp là tĩnh.

Bây giờ tôi không hiểu nó. Bất cứ ai có thể làm sáng tỏ về điều này?



Xem stackoverflow.com/questions/814878/… và nhiều câu hỏi StackOverflow khác về chủ đề này.
Ian Mercer

@Cao tôi có. Đó chỉ là phần tôi trích xuất từ ​​Trạm C # khiến tôi bối rối.
999999

Câu trả lời:


87

Nguồn gốc của sự nhầm lẫn của bạn dường như là có một lỗi đánh máy trong trích xuất từ ​​trạm C #, nên đọc: "... ngoại trừ việc Equals chỉ hoạt động trên các cá thể đối tượng. Phương thức ReferenceEquals là tĩnh."


Bạn nói đúng một cách lỏng lẻo về sự khác biệt trong ý nghĩa ngữ nghĩa của mỗi loại (mặc dù "các trường hợp khác nhau của cùng một đối tượng" có vẻ hơi nhầm lẫn, nhưng có lẽ bạn nên đọc "các trường hợp khác nhau cùng loại ) và về cái có thể bị ghi đè.

Nếu chúng ta bỏ điều đó sang một bên, hãy giải quyết phần cuối cùng của câu hỏi của bạn, tức là cách chúng hoạt động với các System.Objectthể hiện và System.Objecttham chiếu đơn giản (chúng ta cần cả hai để tránh bản chất không đa hình của ==). Ở đây, cả ba thao tác sẽ hoạt động tương đương nhau , nhưng với một lưu ý: Equalskhông thể gọi null.

Equalslà một phương thức thể hiện nhận một tham số ( có thểnull). Vì nó là một phương thức thể hiện (phải được gọi trên một đối tượng thực), nó không thể được gọi trên một null-reference.

ReferenceEqualslà một phương thức tĩnh nhận hai tham số, hoặc / cả hai đều có thể là null. Vì nó là tĩnh (không liên quan đến một đối tượng dụ ), nó sẽ không ném một NullReferenceExceptiontrong bất kỳ hoàn cảnh nào.

==là một toán tử, trong trường hợp này ( object), hoạt động giống hệt với ReferenceEquals. Nó cũng sẽ không ném NullReferenceException.

Để minh họa:

object o1 = null;
object o2 = new object();

//Technically, these should read object.ReferenceEquals for clarity, but this is redundant.
ReferenceEquals(o1, o1); //true
ReferenceEquals(o1, o2); //false
ReferenceEquals(o2, o1); //false
ReferenceEquals(o2, o2); //true

o1.Equals(o1); //NullReferenceException
o1.Equals(o2); //NullReferenceException
o2.Equals(o1); //false
o2.Equals(o2); //true

Vậy đoạn trích từ đài C # được trích dẫn ở trên có sai không (nhất là tôi ghi đè .Equals())?
999999

1
Đoạn trích nêu "trong objectlớp" . Tôi nghĩ bạn đã bỏ qua phần đó? Bởi vì nếu không bạn sẽ không nói về việc ghi đè nó.
Domenic

1
Câu trả lời của tôi chỉ là về objectlớp học.
Ani

@Ani: câu dưới đây của bạn sai. Phương thức static có thể ném NullReferenceException: Vì nó là static (không liên quan đến một đối tượng), nó sẽ không ném aNullReferenceException trong bất kỳ trường hợp nào.
selvaraj

2
Equalscũng là một phương thức tĩnh objectnhận hai tham số. Một hoặc cả hai sau đó có thể được null.
weston,

20

Hãy xem bài báo MSDN về chủ đề này.

Tôi nghĩ những điểm thích hợp là:

Để kiểm tra sự bình đẳng tham chiếu, hãy sử dụng ReferenceEquals. Để kiểm tra sự bình đẳng về giá trị, hãy sử dụng Equals hoặc Equals.

Theo mặc định, toán tử == kiểm tra tính bình đẳng tham chiếu bằng cách xác định xem hai tham chiếu có chỉ ra cùng một đối tượng hay không, do đó, các loại tham chiếu không cần triển khai toán tử == để đạt được chức năng này. Khi một kiểu là bất biến, nghĩa là không thể thay đổi dữ liệu chứa trong cá thể, việc nạp chồng toán tử == để so sánh bình đẳng giá trị thay vì bình đẳng tham chiếu có thể hữu ích bởi vì, là đối tượng bất biến, chúng có thể được coi là giống nhau miễn là chúng có cùng giá trị.

Hi vọng điêu nay co ich!


6
thật không may, liên kết đã chết. +1 để sao chép thông tin có liên quan.
Pac0

6

Sự hiểu biết của bạn về .ReferenceEquals là đúng.

.Equals kiểm tra bình đẳng dữ liệu cho các kiểu giá trị và bình đẳng tham chiếu cho các kiểu không phải giá trị (đối tượng chung).

.Equals có thể được ghi đè để các đối tượng thực hiện một số hình thức kiểm tra bình đẳng dữ liệu

CHỈNH SỬA: Ngoài ra, không thể sử dụng .ReferenceEquals trên các loại giá trị (cũng có thể, nhưng sẽ luôn là sai)


3

Muốn thêm năm xu của tôi về việc so sánh với "null".

  1. ReferenceEquals (object, object) giống như "(object) arg1 == arg2" (vì vậy trong trường hợp các loại giá trị, bạn nhận được quyền anh và nó mất thời gian). Nhưng phương pháp này là cách an toàn 100% duy nhất để kiểm tra đối số của bạn xem có giá trị nào không trong một số trường hợp, như

    • a) trước khi gọi nó là thành viên qua. nhà điều hành
    • b) kiểm tra kết quả của toán tử AS.
  2. == và Equals (). Tại sao tôi lại nói rằng ReferenceEquals là 100% an toàn với kiểm tra null? Hãy tưởng tượng bạn viết các phần mở rộng chung chung trong các libs liên dự án cốt lõi và giả sử bạn hạn chế loại tham số chung cho một số loại miền. Kiểu này có thể giới thiệu toán tử "==" - bây giờ hoặc sau này (và tin tôi đi, tôi đã thấy nhiều, toán tử này có thể có một logic rất "lạ", đặc biệt nếu nói đến miền hoặc các đối tượng liên tục). Bạn cố gắng kiểm tra đối số của bạn cho null và sau đó gọi hoạt động thành viên trên đó. Thật ngạc nhiên, bạn CÓ THỂ có NullRef ở đây. Vì toán tử == gần giống với Equals () - rất tùy chỉnh và rất khó đoán. Tuy nhiên, có một sự khác biệt cần được tính đến - nếu bạn không giới hạn thông số chung của mình cho một số loại tùy chỉnh (== chỉ có thể được sử dụng nếu loại của bạn là "lớp"), toán tử == giống với đối tượng . ReferenceEquals (..). Việc triển khai bằng luôn được sử dụng từ kiểu cuối cùng, vì nó ảo.

Vì vậy, khuyến nghị của tôi là, khi bạn viết các kiểu của riêng mình hoặc bắt nguồn từ các kiểu nổi tiếng, bạn có thể sử dụng == để kiểm tra null. Nếu không, hãy sử dụng object.ReferenceEquals (arg, null).


1

Trong lớp Object .Equals thực hiện danh tính, không bình đẳng. Nó kiểm tra xem các tham chiếu có bằng nhau không. Mã có thể như thế này:

public virtual Boolean Equals(Object other) {
    if (this == other) return true;
    return false;
}

Trong khi triển khai .Equals trong lớp của bạn, bạn nên gọi lớp cơ sở .Equals chỉ khi lớp cơ sở không phải là Đối tượng. Đúng là phức tạp.

Thậm chí nhiều hơn nữa, vì các lớp dẫn xuất có thể ghi đè .Equals và do đó bạn không thể gọi nó để kiểm tra danh tính Microsoft đã thêm phương thức .ReferenceEquals tĩnh.

Nếu bạn sử dụng một số lớp thì đối với bạn một cách hợp lý .Equals kiểm tra sự bình đẳng và .ReferenceEquals kiểm tra danh tính.


1

Tôi đã mở rộng câu trả lời tuyệt vời của Ani để chỉ ra sự khác biệt chính khi xử lý các loại tham chiếu và các phương pháp bình đẳng bị ghi đè.

  • Bạn có thể xem phiên bản làm việc của mã này tại đây: https://dotnetfiddle.net/dFKMhB
  • Ngoài ra, mã này được dán vào LinqPad và chạy dưới dạng Language: C# Program.

.

void Main()
{

    //odd os are null; evens are not null
    object o1 = null;
    object o2 = new object();
    object o3 = null;
    object o4 = new object();
    object o5 = o1;
    object o6 = o2;

    Demo d1 = new Demo(Guid.Empty);
    Demo d2 = new Demo(Guid.NewGuid());
    Demo d3 = new Demo(Guid.Empty);

    Debug.WriteLine("comparing null with null always yields true...");
    ShowResult("ReferenceEquals(o1, o1)", () => ReferenceEquals(o1, o1)); //true
    ShowResult("ReferenceEquals(o3, o1)", () => ReferenceEquals(o3, o1)); //true
    ShowResult("ReferenceEquals(o5, o1)", () => ReferenceEquals(o5, o1)); //true 
    ShowResult("o1 == o1", () => o1 == o1); //true
    ShowResult("o3 == o1", () => o3 == o1); //true
    ShowResult("o5 == o1", () => o5 == o1); //true 

    Debug.WriteLine("...though because the object's null, we can't call methods on the object (i.e. we'd get a null reference exception).");
    ShowResult("o1.Equals(o1)", () => o1.Equals(o1)); //NullReferenceException
    ShowResult("o1.Equals(o2)", () => o1.Equals(o2)); //NullReferenceException
    ShowResult("o3.Equals(o1)", () => o3.Equals(o1)); //NullReferenceException
    ShowResult("o3.Equals(o2)", () => o3.Equals(o2)); //NullReferenceException
    ShowResult("o5.Equals(o1)", () => o5.Equals(o1));  //NullReferenceException
    ShowResult("o5.Equals(o2)", () => o5.Equals(o1));  //NullReferenceException

    Debug.WriteLine("Comparing a null object with a non null object always yeilds false");
    ShowResult("ReferenceEquals(o1, o2)", () => ReferenceEquals(o1, o2)); //false
    ShowResult("ReferenceEquals(o2, o1)", () => ReferenceEquals(o2, o1)); //false
    ShowResult("ReferenceEquals(o3, o2)", () => ReferenceEquals(o3, o2)); //false
    ShowResult("ReferenceEquals(o4, o1)", () => ReferenceEquals(o4, o1)); //false
    ShowResult("ReferenceEquals(o5, o2)", () => ReferenceEquals(o3, o2)); //false
    ShowResult("ReferenceEquals(o6, o1)", () => ReferenceEquals(o4, o1)); //false
    ShowResult("o1 == o2)", () => o1 == o2); //false
    ShowResult("o2 == o1)", () => o2 == o1); //false
    ShowResult("o3 == o2)", () => o3 == o2); //false
    ShowResult("o4 == o1)", () => o4 == o1); //false
    ShowResult("o5 == o2)", () => o3 == o2); //false
    ShowResult("o6 == o1)", () => o4 == o1); //false
    ShowResult("o2.Equals(o1)", () => o2.Equals(o1)); //false
    ShowResult("o4.Equals(o1)", () => o4.Equals(o1)); //false
    ShowResult("o6.Equals(o1)", () => o4.Equals(o1)); //false

    Debug.WriteLine("(though again, we can't call methods on a null object:");
    ShowResult("o1.Equals(o2)", () => o1.Equals(o2)); //NullReferenceException
    ShowResult("o1.Equals(o4)", () => o1.Equals(o4)); //NullReferenceException
    ShowResult("o1.Equals(o6)", () => o1.Equals(o6)); //NullReferenceException

    Debug.WriteLine("Comparing 2 references to the same object always yields true");
    ShowResult("ReferenceEquals(o2, o2)", () => ReferenceEquals(o2, o2)); //true    
    ShowResult("ReferenceEquals(o6, o2)", () => ReferenceEquals(o6, o2)); //true <-- Interesting
    ShowResult("o2 == o2", () => o2 == o2); //true  
    ShowResult("o6 == o2", () => o6 == o2); //true <-- Interesting
    ShowResult("o2.Equals(o2)", () => o2.Equals(o2)); //true 
    ShowResult("o6.Equals(o2)", () => o6.Equals(o2)); //true <-- Interesting

    Debug.WriteLine("However, comparing 2 objects may yield false even if those objects have the same values, if those objects reside in different address spaces (i.e. they're references to different objects, even if the values are similar)");
    Debug.WriteLine("NB: This is an important difference between Reference Types and Value Types.");
    ShowResult("ReferenceEquals(o4, o2)", () => ReferenceEquals(o4, o2)); //false <-- Interesting
    ShowResult("o4 == o2", () => o4 == o2); //false <-- Interesting
    ShowResult("o4.Equals(o2)", () => o4.Equals(o2)); //false <-- Interesting

    Debug.WriteLine("We can override the object's equality operator though, in which case we define what's considered equal");
    Debug.WriteLine("e.g. these objects have different ids, so we treat as not equal");
    ShowResult("ReferenceEquals(d1,d2)",()=>ReferenceEquals(d1,d2)); //false
    ShowResult("ReferenceEquals(d2,d1)",()=>ReferenceEquals(d2,d1)); //false
    ShowResult("d1 == d2",()=>d1 == d2); //false
    ShowResult("d2 == d1",()=>d2 == d1); //false
    ShowResult("d1.Equals(d2)",()=>d1.Equals(d2)); //false
    ShowResult("d2.Equals(d1)",()=>d2.Equals(d1)); //false
    Debug.WriteLine("...whilst these are different objects with the same id; so we treat as equal when using the overridden Equals method...");
    ShowResult("d1.Equals(d3)",()=>d1.Equals(d3)); //true <-- Interesting (sort of; different to what we saw in comparing o2 with o6; but is just running the code we wrote as we'd expect)
    ShowResult("d3.Equals(d1)",()=>d3.Equals(d1)); //true <-- Interesting (sort of; different to what we saw in comparing o2 with o6; but is just running the code we wrote as we'd expect)
    Debug.WriteLine("...but as different when using the other equality tests.");
    ShowResult("ReferenceEquals(d1,d3)",()=>ReferenceEquals(d1,d3)); //false <-- Interesting (sort of; same result we had comparing o2 with o6; but shows that ReferenceEquals does not use the overridden Equals method)
    ShowResult("ReferenceEquals(d3,d1)",()=>ReferenceEquals(d3,d1)); //false <-- Interesting (sort of; same result we had comparing o2 with o6; but shows that ReferenceEquals does not use the overridden Equals method)
    ShowResult("d1 == d3",()=>d1 == d3); //false <-- Interesting (sort of; same result we had comparing o2 with o6; but shows that ReferenceEquals does not use the overridden Equals method)
    ShowResult("d3 == d1",()=>d3 == d1); //false <-- Interesting (sort of; same result we had comparing o2 with o6; but shows that ReferenceEquals does not use the overridden Equals method)


    Debug.WriteLine("For completeness, here's an example of overriding the == operator (wihtout overriding the Equals method; though in reality if overriding == you'd probably want to override Equals too).");
    Demo2 d2a = new Demo2(Guid.Empty);
    Demo2 d2b = new Demo2(Guid.NewGuid());
    Demo2 d2c = new Demo2(Guid.Empty);
    ShowResult("d2a == d2a", () => d2a == d2a); //true
    ShowResult("d2b == d2a", () => d2b == d2a); //false
    ShowResult("d2c == d2a", () => d2c == d2a); //true <-- interesting
    ShowResult("d2a != d2a", () => d2a != d2a); //false
    ShowResult("d2b != d2a", () => d2b != d2a); //true
    ShowResult("d2c != d2a", () => d2c != d2a); //false <-- interesting
    ShowResult("ReferenceEquals(d2a,d2a)", () => ReferenceEquals(d2a, d2a)); //true
    ShowResult("ReferenceEquals(d2b,d2a)", () => ReferenceEquals(d2b, d2a)); //false
    ShowResult("ReferenceEquals(d2c,d2a)", () => ReferenceEquals(d2c, d2a)); //false <-- interesting
    ShowResult("d2a.Equals(d2a)", () => d2a.Equals(d2a)); //true
    ShowResult("d2b.Equals(d2a)", () => d2b.Equals(d2a)); //false
    ShowResult("d2c.Equals(d2a)", () => d2c.Equals(d2a)); //false <-- interesting   

}



//this code's just used to help show the output in a friendly manner
public delegate bool Statement();
void ShowResult(string statementText, Statement statement)
{
    try 
    {
        Debug.WriteLine("\t{0} => {1}",statementText, statement());
    }
    catch(Exception e)
    {
        Debug.WriteLine("\t{0} => throws {1}",statementText, e.GetType());
    }
}

class Demo
{
    Guid id;
    public Demo(Guid id) { this.id = id; }
    public override bool Equals(object obj)
    {
        return Equals(obj as Demo); //if objects are of non-comparable types, obj will be converted to null
    }
    public bool Equals(Demo obj)
    {
        if (obj == null)
        {
            return false;
        }
        else
        {
            return id.Equals(obj.id);
        }
    }
    //if two objects are Equal their hashcodes must be equal
    //however, if two objects hash codes are equal it is not necessarily true that the objects are equal
    //i.e. equal objects are a subset of equal hashcodes
    //more info here: https://stackoverflow.com/a/371348/361842
    public override int GetHashCode()
    {
        return id.GetHashCode();
    }
}

class Demo2
{
    Guid id;
    public Demo2(Guid id)
    {
        this.id = id;
    }

    public static bool operator ==(Demo2 obj1, Demo2 obj2)
    {
        if (ReferenceEquals(null, obj1)) 
        {
            return ReferenceEquals(null, obj2); //true if both are null; false if only obj1 is null
        }
        else
        {
            if(ReferenceEquals(null, obj2)) 
            {
                return false; //obj1 is not null, obj2 is; therefore false
            }
            else
            {
                return obj1.id == obj2.id; //return true if IDs are the same; else return false
            }
        }
    }

    // NB: We also HAVE to override this as below if overriding the == operator; this is enforced by the compiler.  However, oddly we could choose to override it different to the below; but typically that would be a bad idea...
    public static bool operator !=(Demo2 obj1, Demo2 obj2)
    {
        return !(obj1 == obj2);
    }
}

-3

Equals()kiểm tra mã băm hoặc tính tương đương tùy thuộc vào loại cơ bản (Giá trị / Tham chiếu) và ReferenceEquals()nhằm mục đích luôn kiểm tra mã băm. ReferenceEqualstrả về truenếu cả hai đối tượng trỏ đến cùng một vị trí bộ nhớ.

double e = 1.5;
double d = e;
object o1 = d;
object o2 = d;

Console.WriteLine(o1.Equals(o2)); // True
Console.WriteLine(Object.Equals(o1, o2)); // True
Console.WriteLine(Object.ReferenceEquals(o1, o2)); // False

Console.WriteLine(e.Equals(d)); // True
Console.WriteLine(Object.Equals(e, d)); // True
Console.WriteLine(Object.ReferenceEquals(e, d)); // False

3
Thật vô nghĩa. Cả Equals và ReferenceEquals đều không nhìn vào HashCode. Đơn giản là có một yêu cầu rằng các đối tượng HashCodes of Equals phải bằng nhau. Và các đối tượng không trỏ đến bất cứ đâu ... ReferenceEquals đúng nếu và chỉ khi cả hai đối số của nó là cùng một đối tượng tham chiếu hoặc cả hai đều rỗng.
Jim Balter,
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.