What is "Best Practice" For Comparing Two Instances of a Reference Type?

30,980

Solution 1

It looks like you're coding in C#, which has a method called Equals that your class should implement, should you want to compare two objects using some other metric than "are these two pointers (because object handles are just that, pointers) to the same memory address?".

I grabbed some sample code from here:

class TwoDPoint : System.Object
{
    public readonly int x, y;

    public TwoDPoint(int x, int y)  //constructor
    {
        this.x = x;
        this.y = y;
    }

    public override bool Equals(System.Object obj)
    {
        // If parameter is null return false.
        if (obj == null)
        {
            return false;
        }

        // If parameter cannot be cast to Point return false.
        TwoDPoint p = obj as TwoDPoint;
        if ((System.Object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (x == p.x) && (y == p.y);
    }

    public bool Equals(TwoDPoint p)
    {
        // If parameter is null return false:
        if ((object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (x == p.x) && (y == p.y);
    }

    public override int GetHashCode()
    {
        return x ^ y;
    }
}

Java has very similar mechanisms. The equals() method is part of the Object class, and your class overloads it if you want this type of functionality.

The reason overloading '==' can be a bad idea for objects is that, usually, you still want to be able to do the "are these the same pointer" comparisons. These are usually relied upon for, for instance, inserting an element into a list where no duplicates are allowed, and some of your framework stuff may not work if this operator is overloaded in a non-standard way.

Solution 2

Implementing equality in .NET correctly, efficiently and without code duplication is hard. Specifically, for reference types with value semantics (i.e. immutable types that treat equvialence as equality), you should implement the System.IEquatable<T> interface, and you should implement all the different operations (Equals, GetHashCode and ==, !=).

As an example, here’s a class implementing value equality:

class Point : IEquatable<Point> {
    public int X { get; }
    public int Y { get; }

    public Point(int x = 0, int y = 0) { X = x; Y = y; }

    public bool Equals(Point other) {
        if (other is null) return false;
        return X.Equals(other.X) && Y.Equals(other.Y);
    }

    public override bool Equals(object obj) => Equals(obj as Point);

    public static bool operator ==(Point lhs, Point rhs) => object.Equals(lhs, rhs);

    public static bool operator !=(Point lhs, Point rhs) => ! (lhs == rhs);

    public override int GetHashCode() => X.GetHashCode() ^ Y.GetHashCode();
}

The only movable parts in the above code are the bolded parts: the second line in Equals(Point other) and the GetHashCode() method. The other code should remain unchanged.

For reference classes that do not represent immutable values, do not implement the operators == and !=. Instead, use their default meaning, which is to compare object identity.

The code intentionally equates even objects of a derived class type. Often, this might not be desirable because equality between the base class and derived classes is not well-defined. Unfortunately, .NET and the coding guidelines are not very clear here. The code that Resharper creates, posted in another answer, is susceptible to undesired behaviour in such cases because Equals(object x) and Equals(SecurableResourcePermission x) will treat this case differently.

In order to change this behaviour, an additional type check has to be inserted in the strongly-typed Equals method above:

public bool Equals(Point other) {
    if (other is null) return false;
    if (other.GetType() != GetType()) return false;
    return X.Equals(other.X) && Y.Equals(other.Y);
}

Solution 3

Below I have summed up what you need to do when implementing IEquatable and provided the justification from the various MSDN documentation pages.


Summary

  • When testing for value equality is desired (such as when using objects in collections) you should implement the IEquatable interface, override Object.Equals, and GetHashCode for your class.
  • When testing for reference equality is desired you should use operator==,operator!= and Object.ReferenceEquals.
  • You should only override operator== and operator!= for ValueTypes and immutable reference types.

Justification

IEquatable

The System.IEquatable interface is used to compare two instances of an object for equality. The objects are compared based on the logic implemented in the class. The comparison results in a boolean value indicating if the objects are different. This is in contrast to the System.IComparable interface, which return an integer indicating how the object values are different.

The IEquatable interface declares two methods that must be overridden. The Equals method contains the implementation to perform the actual comparison and return true if the object values are equal, or false if they are not. The GetHashCode method should return a unique hash value that may be used to uniquely identify identical objects that contain different values. The type of hashing algorithm used is implementation-specific.

IEquatable.Equals Method

  • You should implement IEquatable for your objects to handle the possibility that they will be stored in an array or generic collection.
  • If you implement IEquatable you should also override the base class implementations of Object.Equals(Object) and GetHashCode so that their behavior is consistent with that of the IEquatable.Equals method

Guidelines for Overriding Equals() and Operator == (C# Programming Guide)

  • x.Equals(x) returns true.
  • x.Equals(y) returns the same value as y.Equals(x)
  • if (x.Equals(y) && y.Equals(z)) returns true, then x.Equals(z) returns true.
  • Successive invocations of x. Equals (y) return the same value as long as the objects referenced by x and y are not modified.
  • x. Equals (null) returns false (for non-nullable value types only. For more information, see Nullable Types (C# Programming Guide).)
  • The new implementation of Equals should not throw exceptions.
  • It is recommended that any class that overrides Equals also override Object.GetHashCode.
  • Is is recommended that in addition to implementing Equals(object), any class also implement Equals(type) for their own type, to enhance performance.

By default, the operator == tests for reference equality by determining whether two references indicate the same object. Therefore, reference types do not have to implement operator == in order to gain this functionality. When a type is immutable, that is, the data that is contained in the instance cannot be changed, overloading operator == to compare value equality instead of reference equality can be useful because, as immutable objects, they can be considered the same as long as they have the same value. It is not a good idea to override operator == in non-immutable types.

  • Overloaded operator == implementations should not throw exceptions.
  • Any type that overloads operator == should also overload operator !=.

== Operator (C# Reference)

  • For predefined value types, the equality operator (==) returns true if the values of its operands are equal, false otherwise.
  • For reference types other than string, == returns true if its two operands refer to the same object.
  • For the string type, == compares the values of the strings.
  • When testing for null using == comparisons within your operator== overrides, make sure you use the base object class operator. If you don't, infinite recursion will occur resulting in a stackoverflow.

Object.Equals Method (Object)

If your programming language supports operator overloading and if you choose to overload the equality operator for a given type, that type must override the Equals method. Such implementations of the Equals method must return the same results as the equality operator

The following guidelines are for implementing a value type:

  • Consider overriding Equals to gain increased performance over that provided by the default implementation of Equals on ValueType.
  • If you override Equals and the language supports operator overloading, you must overload the equality operator for your value type.

The following guidelines are for implementing a reference type:

  • Consider overriding Equals on a reference type if the semantics of the type are based on the fact that the type represents some value(s).
  • Most reference types must not overload the equality operator, even if they override Equals. However, if you are implementing a reference type that is intended to have value semantics, such as a complex number type, you must override the equality operator.

Additional Gotchas

Solution 4

That article just recommends against overriding the equality operator (for reference types), not against overriding Equals. You should override Equals within your object (reference or value) if equality checks will mean something more than reference checks. If you want an interface, you can also implement IEquatable (used by generic collections). If you do implement IEquatable, however, you should also override equals, as the IEquatable remarks section states:

If you implement IEquatable<T>, you should also override the base class implementations of Object.Equals(Object) and GetHashCode so that their behavior is consistent with that of the IEquatable<T>.Equals method. If you do override Object.Equals(Object), your overridden implementation is also called in calls to the static Equals(System.Object, System.Object) method on your class. This ensures that all invocations of the Equals method return consistent results.

In regards to whether you should implement Equals and/or the equality operator:

From Implementing the Equals Method

Most reference types should not overload the equality operator, even if they override Equals.

From Guidelines for Implementing Equals and the Equality Operator (==)

Override the Equals method whenever you implement the equality operator (==), and make them do the same thing.

This only says that you need to override Equals whenever you implement the equality operator. It does not say that you need to override the equality operator when you override Equals.

Solution 5

For complex objects that will yield specific comparisons then implementing IComparable and defining the comparison in the Compare methods is a good implementation.

For example we have "Vehicle" objects where the only difference may be the registration number and we use this to compare to ensure that the expected value returned in testing is the one we want.

Share:
30,980
Rob Cooper
Author by

Rob Cooper

I R GUY Available on twitter @robcthegeek

Updated on July 09, 2022

Comments

  • Rob Cooper
    Rob Cooper almost 2 years

    I came across this recently, up until now I have been happily overriding the equality operator (==) and/or Equals method in order to see if two references types actually contained the same data (i.e. two different instances that look the same).

    I have been using this even more since I have been getting more in to automated testing (comparing reference/expected data against that returned).

    While looking over some of the coding standards guidelines in MSDN I came across an article that advises against it. Now I understand why the article is saying this (because they are not the same instance) but it does not answer the question:

    1. What is the best way to compare two reference types?
    2. Should we implement IComparable? (I have also seen mention that this should be reserved for value types only).
    3. Is there some interface I don't know about?
    4. Should we just roll our own?!

    Many Thanks ^_^

    Update

    Looks like I had mis-read some of the documentation (it's been a long day) and overriding Equals may be the way to go..

    If you are implementing reference types, you should consider overriding the Equals method on a reference type if your type looks like a base type such as a Point, String, BigNumber, and so on. Most reference types should not overload the equality operator, even if they override Equals. However, if you are implementing a reference type that is intended to have value semantics, such as a complex number type, you should override the equality operator.

  • Rob Cooper
    Rob Cooper over 15 years
    Good answer, thank you. I am glad you added the bit about why not to overload the equality operator.
  • Rob Cooper
    Rob Cooper over 15 years
    Thanks for this, Paul. Noted on the IComparable interface although I think in this instance it will probably be overkill since I want to just check for equality.
  • Rob Cooper
    Rob Cooper over 15 years
    Good Answer Konrad, too good for this question really (i.e. I only really need the equality side of things)! Nice answer though, +1 from me. :)
  • Konrad Rudolph
    Konrad Rudolph over 15 years
    This is actually one of the weaknesses of C#. As long as the implementor follows the guidelines, though, this isn't an issue because the semantics of == won't be changed for equal references. Still, I find myself using object.ReferenceEquals in critical situations in C# (VB has Is instead).
  • Lemon
    Lemon about 15 years
    How do you make ReSharper make that stuff automatically?
  • Asim Ihsan
    Asim Ihsan about 15 years
    For classes, why are you overriding the equality and inequality operators to perform a reference comparison, when that functionality is provided by default by the System.Object base class?
  • Konrad Rudolph
    Konrad Rudolph about 15 years
    @Burly: I don’t. They should perform value comparison (notice the lhs.Equals(rhs) at the end)! The ReferenceEquals checks are merely a time-saver (under certain circumstances) because if two references are equal we don’t need to check for value equality as well (x == x, always).
  • Asim Ihsan
    Asim Ihsan about 15 years
    Ahh, I missed the lhs.Equals(rhs) at the end. It is considered best practice /not/ to implement value equality on the == and != operators for reference types that are non-immutable. You should however, override them for value types. See my answer below for the source and discussion of this.
  • Konrad Rudolph
    Konrad Rudolph about 15 years
    It is considered best practice to make Equals and == perform equivalent actions, always. This is reflected in my code snippet. Obviously, only use it if such semantics make sense. But always make Equals and == perform in a consistent way. It's an absolute usability horror if they don't.
  • Konrad Rudolph
    Konrad Rudolph about 15 years
    And yes, I am aware that you stated something else: I strongly disagree with that statement.
  • Asim Ihsan
    Asim Ihsan about 15 years
    Why do you think that Equals and == should be consistent? This goes against what the MSDN documentation states and it also creates a similar disconnect where == no longer means reference equality. This creates a similar usability issue since this behavior is uniformly provided by .NET.
  • Asim Ihsan
    Asim Ihsan about 15 years
    FWIW, I can certainly see where you are coming from, especially coming from the C++ world myself. However, since the MSDN documentation/guidelines explicitly recommends against what you are doing, I'm searching for a solid argument in favor of your position. Perhaps this deserves it's own question..
  • nawfal
    nawfal over 11 years
    + for the snippet, excellent! A minor suggestion: Couldn't you move object.ReferenceEquals(lhs, rhs) from == to generic Equals? So that gives the speed (of course it depends, but assuming the worst case) checking of reference equality even for generic Equals method?
  • nawfal
    nawfal over 11 years
    You shouldnt be writing the equality logic at two places. Not sure how MS got it incorrect..
  • Konrad Rudolph
    Konrad Rudolph over 11 years
    @nawfal No, we need it in ==, otherwise we would get the wrong result for ((Point) null) == ((Point) null); namely, it would return false which is an error. We could also perform the check in Equals, of course. In fact, in cases where that makes sense it should absolutely be done.
  • nawfal
    nawfal over 11 years
    @KonradRudolph I knew moving out that code as such will produce bugs, but I'm thinking we can tweak that a bit to be error free. I did something similar in my answer. I think its better (in that regard).
  • supercat
    supercat over 11 years
    A major problem with overriding the == operator for reference types (due to what is IMHO a defect in the design of C#) is that in C# there are effectively two different operators, and the decision of which operator to use is made statically at compile time. With value types, it's possible to overload == so that it tests value equality in all cases the compiler will accept [4==4.0m and 4==4.0 compile and yield true, but 4.0m==4.0 won't compile]. That is not possible with reference types; given var s1="1"; var s2=1.ToString(); Object o1 = s1;, s1==s2 and o1==s1, but o1!=s2.
  • supercat
    supercat about 11 years
    The use of the same name for Equals(Object) and Equals(OwnType) is perhaps unfortunate, since in many cases, because of implicit typecasts, neither Equals(OwnType) nor the == operator can define an equivalence relation. If I'd designed .net, the Object method would be named EquivalentTo, and overrides would be expected to use stricter equivalence standards. For example, I'd specify that 1.0m.EquivalentTo(1.00m) should be false, but 1.0m.Equals(1.00m) and 1.0m == 1.00m should be true since the values are numerically equal even though they are not equivalent.
  • Konrad Rudolph
    Konrad Rudolph over 10 years
    @nawfal Yes, unfortunately my host has gone offline a few months ago and so far I haven’t been able to contact anybody to fix this. I still hope that they will react at some point.
  • nawfal
    nawfal over 10 years
    @KonradRudolph sad, can't u upload it somewhere else?
  • Konrad Rudolph
    Konrad Rudolph over 10 years
    @nawfal I don’t have the code any more myself, I would need to access my web space as well … :(
  • kofifus
    kofifus almost 6 years
    shouldn't Equals check for ReferenceEquals(this, other) ? in case someone does Equals(this) ? (instead of in ==)
  • Konrad Rudolph
    Konrad Rudolph almost 6 years
    @kofifus You can but it’s redundant, and probably inefficient (self equality checks are expected to be rare, and take up time, too)
  • kofifus
    kofifus almost 6 years
    thanks.. also visual studio suggests replacing (object.ReferenceEquals(other, null)) with (other is null) - is that ok ?
  • Konrad Rudolph
    Konrad Rudolph almost 6 years
    @kofifus Are you using VB? Then, yes, you can/should replace all usage of object.ReferenceEquals by the Is operator. In C#, you can’t because pattern matching is invokes Object.Equals under the hood, which, in turn calls the Equals method we’re in the process of overriding. So you’d get an infinite recursion. If Visual Studio’s gives you this advice in C# then that advice is unfortunately wrong.
  • kofifus
    kofifus almost 6 years
    yeah visual studio 2017 C#
  • Konrad Rudolph
    Konrad Rudolph almost 6 years
    @kofifus Actually never mind: I tested it and other is null works. Apparently the static Object.Equals performs its own (strictly speaking redundant!) null check on the second argument, and therefore does not end up calling our overridden Equals method (see also its documentation). So yes, do replace it as suggested by VS. But note that this will only work in C# 7.0 or later. Previous versions of C# didn’t support this usage of is.
  • kofifus
    kofifus almost 6 years
    ok great and thanks ! I posted a version of your answer above
  • kofifus
    kofifus almost 6 years
    I think there is an edge case not covered: assume classes Base and Derived, and assume (new Derived(1)).Equals((new Derived(1)))==true then Base mybase1 = new Derived(1); Base mybase2 = new Derived(1); mybase1.Equals(mybase2) should return true
  • Konrad Rudolph
    Konrad Rudolph almost 6 years
    @kofifus That case is covered, too. Along with every other combination of base/derived comparison. Furthermore, this works correctly (a) if only Base overrides equality comparison and implements IEquatable<Base>, as well as (b) if Derived also overrides the comparison and implements IEquatable<Derived>.
  • kofifus
    kofifus almost 6 years
    I see your edit, why did you remove the ReferenceEquals test ?
  • Konrad Rudolph
    Konrad Rudolph almost 6 years