== vs. Object.Equals(object) in .NET

前端 未结 9 2193
无人及你
无人及你 2020-11-28 22:33

So, when I was a comparative novice to the novice I am right now, I used to think that these two things were syntactic sugar for each other, i.e. that using one over the oth

相关标签:
9条回答
  • 2020-11-28 23:16

    You may want to use .Equals as someone may come along at a later time and overload them for you class.

    0 讨论(0)
  • 2020-11-28 23:22

    I was going to post this as a comment on the accepted answer, but I think this deserves to be considered when determining which route to take.

    dotnetfiddle: https://dotnetfiddle.net/gESLzO

    Fiddle code:

        Object a = null;
        Object b = new Object();
    
        // Ex 1
        Console.WriteLine(a == b);
        // Ex 2
        Console.WriteLine(b == a);
    
        // Ex 3     
        Console.WriteLine(b.Equals(a));
        // Ex 4
        Console.WriteLine(a.Equals(b));
    

    The first 3 WriteLine examples will work, but the fourth throws an exception. 1 and 2 use ==, which is a static method that does not require either object to be instantiated.

    Example 3 works because b is instantiated.

    Example 4 fails because a is null, and thus a method can not be called on a null object.

    Because I try to code as lazily as possible, I use ==, especially when working with scenarios where either object (or both) can be null. If I didn't, I'd have to do a null check, first, before being able to call .Equals().

    0 讨论(0)
  • 2020-11-28 23:23

    Operator == and Equals() both are same while we are comparing values instead of references. Output of both are same see below example.

    Example

        static void Main()
        {
            string x = " hello";
            string y = " hello";
            string z = string.Copy(x);
            if (x == y)
            {
                Console.WriteLine("== Operator");
            }
            if(x.Equals(y))
            {
                Console.WriteLine("Equals() Function Call");
            }
            if (x == z)
            {
                Console.WriteLine("== Operator while coping a string to another.");
            }
            if (x.Equals(y))
            {
                Console.WriteLine("Equals() Function Call while coping a string to another.");
            }
        }
    

    Output:

      == Operator
      Equals() Function Call
      == Operator while coping a string to another.
      Equals() Function Call while coping a string to another.
    
    0 讨论(0)
  • 2020-11-28 23:26

    MSDN has clear and solid descriptions of both things.

    object.Equals method

    operator ==

    Overloadable Operators

    Guidelines for Overriding Equals() and Operator ==

    Is this a good thing, what are the differences, and when/why should you use one over the other?

    How can it be "good" or "bad" thing? One - method, another - operator. If reference equality is not sufficient, overload them, otherwise leave them as is. For primitive types they just work out of box.

    0 讨论(0)
  • 2020-11-28 23:27

    Microsoft says that class implementers should make == behave as similarly as possible to Equals:

    DO ensure that Object.Equals and the equality operators have exactly the same semantics

    from http://msdn.microsoft.com/en-us/library/vstudio/7h9bszxx(v=vs.110).aspx


    If you want to be certain you are getting IDENTITY comparison (when comparing references), then use ReferenceEquals instead.

    If a class implementor does not override ==, then static method is looked for, at compile time, in base classes. If this search reaches Object, then Object.== is used. For classes, this is same as ReferenceEquals.

    If class documentation is uncertain as to whether a given class (from a vendor other than Microsoft presumably) implements == as Equals or ReferenceEquals (or it could in theory be different than both of those), I sometimes avoid ==. Instead, I use the less readable Equals(a, b) or ReferenceEquals(a, b), depending on which meaning I want.

    OTOH, ps2goat makes a good point that using == avoids exception if first operand is null (because == is a static operator). This is an argument in favor of using ==.


    Removed controversial commentary regarding ==


    UPDATE A recent Microsoft doc quote, from .Net 4.7.2 retrieved Feb. 2019, shows they still intend the two to behave similarly:

    Object.Equals Method

    Some languages such as C# and Visual Basic support operator overloading. When a type overloads the equality operator, it must also override the Equals(Object) method to provide the same functionality. This is typically accomplished by writing the Equals(Object) method in terms of the overloaded equality operator, as in the following example.


    NOTE: See other answers for the consequences of == being a static method vs Equals being an instance method. I'm not claiming behavior is identical; I'm observing that Microsoft recommends making the two as similar as possible.

    0 讨论(0)
  • 2020-11-28 23:28

    To answer this, we must describe the four kinds of object equivalence:

    1. Reference Equality, object.ReferenceEquals(a, b): The two variables point to the same exact object in RAM. (If this were C, both variables would have the same exact pointer.)

    2. Interchangeability, a == b: The two variables refer to objects that are completely interchangeable. Thus, when a == b, Func(a,b) and Func(b,a) do the same thing.

    3. Semantic Equality, object.Equals(a, b): At this exact moment in time, the two objects mean the same thing.

    4. Entity equality, a.Id == b.Id: The two objects refer to the same entity, such as a database row, but don’t have to have the same contents.

    As a programmer, when working with an object of a known type, you need to understand the kind of equivalence that’s appropriate for your business logic at the specific moment of code that you’re in.

    The simplest example about this is the string versus StringBuilder types. String overrides ==, StringBuilder does not:

    var aaa1 = "aaa";
    var aaa2 = $"{'a'}{'a'}{'a'}";
    var bbb = "bbb";
    
    // False because aaa1 and aaa2 are completely different objects with different locations in RAM
    Console.WriteLine($"Object.ReferenceEquals(aaa1, aaa2): {Object.ReferenceEquals(aaa1, aaa2)}");
    
    // True because aaa1 and aaa2 are completely interchangable
    Console.WriteLine($"aaa1 == aaa2: {aaa1 == aaa2}");             // True
    Console.WriteLine($"aaa1.Equals(aaa2): {aaa1.Equals(aaa2)}");   // True
    Console.WriteLine($"aaa1 == bbb: {aaa1 == bbb}");               // False
    Console.WriteLine($"aaa1.Equals(bbb): {aaa1.Equals(bbb)}");     // False
    
    // Won't compile
    // This is why string can override ==, you can not modify a string object once it is allocated
    //aaa1[0] = 'd';
    
    // aaaUpdated and aaa1 point to the same exact object in RAM
    var aaaUpdated = aaa1;
    Console.WriteLine($"Object.ReferenceEquals(aaa1, aaaUpdated): {Object.ReferenceEquals(aaa1, aaaUpdated)}"); // True
    
    // aaaUpdated is a new string, aaa1 is unmodified
    aaaUpdated += 'c';
    Console.WriteLine($"Object.ReferenceEquals(aaa1, aaaUpdated): {Object.ReferenceEquals(aaa1, aaaUpdated)}"); // False
    
    var aaaBuilder1 = new StringBuilder("aaa");
    var aaaBuilder2 = new StringBuilder("aaa");
    
    // False, because both string builders are different objects
    Console.WriteLine($"Object.ReferenceEquals(aaaBuider1, aaaBuider2): {Object.ReferenceEquals(aaa1, aaa2)}");
    
    // Even though both string builders have the same contents, they are not interchangable
    // Thus, == is false
    Console.WriteLine($"aaaBuider1 == aaaBuilder2: {aaaBuilder1 == aaaBuilder2}");
    
    // But, because they both have "aaa" at this exact moment in time, Equals returns true
    Console.WriteLine($"aaaBuider1.Equals(aaaBuilder2): {aaaBuilder1.Equals(aaaBuilder2)}");
    
    // Modifying the contents of the string builders changes the strings, and thus
    // Equals returns false
    aaaBuilder1.Append('e');
    aaaBuilder2.Append('f');
    Console.WriteLine($"aaaBuider1.Equals(aaaBuilder2): {aaaBuilder1.Equals(aaaBuilder2)}");
    

    To get into more details, we can work backwards, starting with entity equality. In the case of entity equality, properties of the entity may change over time, but the entity’s primary key never changes. This can be demonstrated with pseudocode:

    // Hold the current user object in a variable
    var originalUser = database.GetUser(123);
    
    // Update the user’s name
    database.UpdateUserName(123, user.Name + "son");
    
    var updatedUser = database.GetUser(123);
    
    Console.WriteLine(originalUser.Id == updatedUser.Id); // True, both objects refer to the same entity
    Console.WriteLine(Object.Equals(originalUser, updatedUser); // False, the name property is different
    

    Moving to semantic equality, the example changes slightly:

    var originalUser = new User() { Name = "George" };
    var updatedUser = new User() { Name = "George" };
    
    Console.WriteLine(Object.Equals(originalUser, updatedUser); // True, the objects have the same contents
    Console.WriteLine(originalUser == updatedUser); // User doesn’t define ==, False
    
    updatedUser.Name = "Paul";
    
    Console.WriteLine(Object.Equals(originalUser, updatedUser); // False, the name property is different
    

    What about interchangeability? (overriding ==) That’s more complicated. Let’s build on the above example a bit:

    var originalUser = new User() { Name = "George" };
    var updatedUser = new User() { Name = "George" };
    Console.WriteLine(Object.Equals(originalUser, updatedUser); // True, the objects have the same contents
    
    // Does this change updatedUser? We don’t know
    DoSomethingWith(updatedUser);
    
    // Are the following equivalent?
    // SomeMethod(originalUser, updatedUser);
    // SomeMethod(updatedUser, originalUser);
    

    In the above example, DoSomethingWithUser(updatedUser) might change updatedUser. Thus we can no longer guarantee that the originalUser and updatedUser objects are "Equals." This is why User does not override ==.

    A good example for when to override == is with immutable objects. An immutable object is an object who’s publicly-visible state (properties) never change. The entire visible state must be set in the object’s constructor. (Thus, all properties are read-only.)

    var originalImmutableUser = new ImmutableUser(name: "George");
    var secondImmutableUser = new ImmutableUser(name: "George");
    
    Console.WriteLine(Object.Equals(originalImmutableUser, secondImmutableUser); // True, the objects have the same contents
    Console.WriteLine(originalImmutableUser == secondImmutableUser); // ImmutableUser defines ==, True
    
    // Won’t compile because ImmutableUser has no setters
    secondImmutableUser.Name = "Paul";
    
    // But this does compile
    var updatedImmutableUser = secondImmutableUser.SetName("Paul"); // Returns a copy of secondImmutableUser with Name changed to Paul.
    
    Console.WriteLine(object.ReferenceEquals(updatedImmutableUser, secondImmutableUser)); // False, because updatedImmutableUser is a different object in a different location in RAM
    
    // These two calls are equivalent because the internal state of an ImmutableUser can never change
    DoSomethingWith(originalImmutableUser, secondImmutableUser);
    DoSomethingWith(secondImmutableUser, originalImmutableUser);
    

    Should you override == with a mutable object? (That is, an object who’s internal state can change?) Probably not. You would need to build a rather complicated event system to maintain interchangeability.

    In general, I work with a lot of code that uses immutable objects, so I override == because it’s more readable than object.Equals. When I work with mutable objects, I don’t override == and rely on object.Equals. Its the programmer’s responsibility to know if the objects they are working with are mutable or not, because knowing if something’s state can change should influence how you design your code.

    The default implementation of == is object.ReferenceEquals because, with mutable objects, interchangeability is only guaranteed when the variables point to the same exact object in RAM. Even if the objects have the same contents at a given point in time, (Equals returns true,) there is no guarantee that the objects will continue to be equal; thus the objects are not interchangeable. Thus, when working with a mutable object that does not override ==, the default implementation of == works, because if a == b, they are the same object, and SomeFunc(a, b) and SomeFunc(b, a) are exactly the same.

    Furthermore, if a class does not define equivalence, (For example, think of a database connection, and open file handle, ect,) then the default implementation of == and Equals fall back to reference equality, because two variables of type database connection, open file handle, ect, are only equal if they are the exact instance of the database connection, open file handle, ect. Entity equality might make sense in business logic that needs to know that two different database connections refer to the same database, or that two different file handles refer to the same file on disk.

    Now, for my soapbox moment. In my opinion, C# handles this topic in a confusing way. == should be for semantic equality, instead of the Equals method. There should be a different operator, like ===, for interchangeability, and potentially another operator, ====, for referential equality. This way, someone who's a novice, and / or writing CRUD applications, only needs to understand ==, and not the more nuanced details of interchangeability and referential equality.

    0 讨论(0)
提交回复
热议问题