Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 846
Rob Cooper Avatar asked Sep 19 '08 18:09

Rob Cooper


2 Answers

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);
}
like image 131
Konrad Rudolph Avatar answered Sep 27 '22 17:09

Konrad Rudolph


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.

like image 42
Matt J Avatar answered Sep 27 '22 18:09

Matt J