What's the Difference Between Iequatable and Just Overriding Object.Equals()

What's the difference between IEquatable and just overriding Object.Equals()?

The main reason is performance. When generics were introduced in .NET 2.0 they were able to add a bunch of neat classes such as List<T>, Dictionary<K,V>, HashSet<T>, etc. These structures make heavy use of GetHashCode and Equals. But for value types this required boxing. IEquatable<T> lets a structure implement a strongly typed Equals method so no boxing is required. Thus much better performance when using value types with generic collections.

Reference types don't benefit as much but the IEquatable<T> implementation does let you avoid a cast from System.Object which can make a difference if it's called frequently.

As noted on Jared Parson's blog though, you must still implement the standard Object.Equals and Object.GetHashcode overrides.

Equals override vs. IEquatable

As pointed by msdn, if you are implementing IEquatable<T> you will still need to override Equals because it will still be called with the signature Equals(System.Object, System.Object) and yhe override should be consistent with the methods implemented from IEquatable<T>.

Also as in the question about the difference between iequatable and just overriding object equals which Arno showed in the comment, IEquatable<T> is used when operations on collections are required to optimize them, not to need the boxing anymore, and instead call the direct Equals with the specific type.

You have two options:

If you are interested in performance when you are working with collections in your program, you could keep implementing the both Equals methods;

or

You could just remove the IEquatable<T> and only override Equals to simplify your code.

Additionally, whenever you override Equals, you should also always override GetHashCode as well.

Differences between IEquatableT, IEqualityComparerT, and overriding .Equals() when using LINQ on a custom object collection?

It doesn't matter whether you override object.Equals and object.GetHashCode, implement IEquatable, or provide an IEqualityComparer. All of them can work, just in slightly different ways.

1) Overriding Equals and GetHashCode from object:

This is the base case, in a sense. It will generally work, assuming you're in a position to edit the type to ensure that the implementation of the two methods are as desired. There's nothing wrong with doing just this in many cases.

2) Implementing IEquatable

The key point here is that you can (and should) implement IEquatable<YourTypeHere>. The key difference between this and #1 is that you have strong typing for the Equals method, rather than just having it use object. This is both better for convenience to the programmer (added type safety) and also means that any value types won't be boxed, so this can improve performance for custom structs. If you do this you should pretty much always do it in addition to #1, not instead of. Having the Equals method here differ in functionality from object.Equals would be...bad. Don't do that.

3) Implementing IEqualityComparer

This is entirely different from the first two. The idea here is that the object isn't getting it's own hash code, or seeing if it's equal to something else. The point of this approach is that an object doesn't know how to properly get it's hash or see if it's equal to something else. Perhaps it's because you don't control the code of the type (i.e. a 3rd party library) and they didn't bother to override the behavior, or perhaps they did override it but you just want your own unique definition of "equality" in this particular context.

In this case you create an entirely separate "comparer" object that takes in two different objects and informs you of whether they are equal or not, or what the hash code of one object is. When using this solution it doesn't matter what the Equals or GetHashCode methods do in the type itself, you won't use it.


Note that all of this is entirely unrelated from the == operator, which is its own beast.

Equals implementation with override Equals, IEquatable, == and !=

You should not repeat yourself: implementing public bool Equals(Foo other) is enough;
all the other: Equals, ==, != can be derived from Equals(Foo other):

public class Foo : IEquatable<Foo>
{
public int Bar { get; set; }

public bool Equals(Foo other)
{
if (ReferenceEquals(this, other)) return true;

if (other is null) return false;

// here all the values should be checked
return Bar == other.Bar;
}

// Here we can put it short
public override bool Equals(object obj) => obj is Foo other && Equals(other);

//TODO: Don't forget about hash
// Here I put the easiest approach which exploits the fact we
// have just one property of type int.
public override int GetHashCode() => Bar;

// Note ReferenceEquals(a, b): we want null == null be true
public static bool operator == (Foo a, Foo b) =>
ReferenceEquals(a, b) || (a is not null && a.Equals(b));

public static bool operator !=(Foo a, Foo b) => !(a == b);
}

Is it important to override Equals if I'm implementing IEquatableT?

From msdn:

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. 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. In
addition, you should overload the op_Equality and op_Inequality
operators. This ensures that all tests for equality return consistent
results.

I could have done a better google search too. Here's a good article on msdn blogs by JaredPar on the subject.

In short, overriding Equals(object obj):

Needed for the static Equals(object obj1, object obj2) method on object class, or for Contains(object item) on ArrayList instance etc..

Accepting this since the link is more thorough on the subject. Thanks to Oded too..

Equals() IEquatable How ther are related to each other?

IEquatable<T> defines

bool Equals(T other)

While object's Equals defines

bool Equals(object other)

So defining IEquatable for your class allows you to do an easier comparison of two items of the same class without having to cast to the current type. A lot of times you'll see people implement IEquatable and then override object's Equals to call IEquatable's equals:

public class SomeClass : IEquatable<SomeClass>
{
public bool Equals(SomeClass other)
{
// actual compare
}

public override bool Equals(object other)
{
// cross-call to IEquatable's equals.
return Equals(other as SomeClass);
}
}

In addition, you can define equality between other types as well. Like make Fraction implement IEquatable

Overriding IEquatableT when T is an interface and hashcodes are different between derived types

Consider making the a IEqualityComparer<> class to compare the common values.

I have renamed the interface to ICommon for readability

public interface ICommon
{
int SomeInt { get; }
bool SomeBool { get; }
float SomeFloat { get; }
}

public class CommonComparer : IEqualityComparer<ICommon>
{
public bool Equals(ICommon x, ICommon y)
{
return x.SomeInt.Equals(y.SomeInt)
&& x.SomeBool.Equals(y.SomeBool)
&& x.SomeFloat.Equals(y.SomeFloat);
}

public int GetHashCode(ICommon obj)
{
unchecked
{
int hc = -1817952719;
hc = (-1521134295)*hc + obj.SomeInt.GetHashCode();
hc = (-1521134295)*hc + obj.SomeBool.GetHashCode();
hc = (-1521134295)*hc + obj.SomeFloat.GetHashCode();
return hc;
}
}
}

and the program can distinguish between the equal items on two lists.

class Program
{
static void Main(string[] args)
{
var listA = new List<A>
{
new A(1001, true, 1.001f, "A1"),
new A(1002, true, 1.002f, "A2"),
new A(1003, false, 1.003f, "A1"),
new A(1004, false, 1.004f, "A4")
};

var listB = new List<B>
{
new B(1001, true, 1.001f, "B1", 2.5),
new B(1002, false, 1.002f, "B2", 2.8),
new B(1003, true, 1.003f, "B3", 2.9),
new B(1004, false, 1.004f, "B4", 2.9)
};

var common = Enumerable.Intersect(listA, listB, new CommonComparer()).OfType<ICommon>();

Console.WriteLine($"{"SomeInt",-8} {"Bool",-6} {"SomeFloat",-10}");
foreach (var item in common)
{
Console.WriteLine($"{item.SomeInt,-8} {item.SomeBool,-6} {item.SomeFloat,-10}");
}
//SomeInt Bool SomeFloat
//1001 True 1.001
//1004 False 1.004

}
}

and the rest of the code definitions

public class A : ICommon, IEquatable<A>
{
static readonly CommonComparer comparer = new CommonComparer();

public int SomeInt { get; }
public bool SomeBool { get; }
public float SomeFloat { get; }

private readonly string _someARelatedStuff;
// Rest of class...
public A(ICommon other, string someARelatedStuff)
: this(other.SomeInt, other.SomeBool, other.SomeFloat, someARelatedStuff)
{ }
public A(int someInt, bool someBool, float someFloat, string someARelatedStuff)
{
this.SomeInt = someInt;
this.SomeBool = someBool;
this.SomeFloat = someFloat;
this._someARelatedStuff = someARelatedStuff;
}

public override string ToString() => _someARelatedStuff;

#region IEquatable Members
public override bool Equals(object obj)
{
if (obj is A other)
{
return Equals(other);
}
return false;
}

public virtual bool Equals(A other)
{
return comparer.Equals(this, other)
&& _someARelatedStuff.Equals(other._someARelatedStuff);
}

public override int GetHashCode()
{
unchecked
{
int hc = comparer.GetHashCode(this);
hc = (-1521134295)*hc + _someARelatedStuff.GetHashCode();
return hc;
}
}

#endregion

}

public class B : ICommon, IEquatable<B>
{
static readonly CommonComparer comparer = new CommonComparer();

public int SomeInt { get; }
public bool SomeBool { get; }
public float SomeFloat { get; }

readonly string _someBRelatedStuff;
readonly double _someOtherBRelatedStuff;
// Rest of class...

public B(ICommon other, string someBRelatedStuff, double someOtherBRelatedStuff)
: this(other.SomeInt, other.SomeBool, other.SomeFloat, someBRelatedStuff, someOtherBRelatedStuff)
{ }
public B(int someInt, bool someBool, float someFloat, string someBRelatedStuff, double someOtherBRelatedStuff)
{
this.SomeInt = someInt;
this.SomeBool = someBool;
this.SomeFloat = someFloat;
this._someBRelatedStuff = someBRelatedStuff;
this._someOtherBRelatedStuff = someOtherBRelatedStuff;
}

public override string ToString() => $"{_someBRelatedStuff}, {_someOtherBRelatedStuff.ToString("g4")}";

#region IEquatable Members

public override bool Equals(object obj)
{
if (obj is B other)
{
return Equals(other);
}
return false;
}

public virtual bool Equals(B other)
{
return comparer.Equals(this, other)
&& _someBRelatedStuff.Equals(other._someBRelatedStuff)
&& _someOtherBRelatedStuff.Equals(other._someOtherBRelatedStuff);
}

public override int GetHashCode()
{
unchecked
{
int hc = comparer.GetHashCode(this);
hc = (-1521134295)*hc + _someBRelatedStuff.GetHashCode();
hc = (-1521134295)*hc + _someOtherBRelatedStuff.GetHashCode();
return hc;
}
}

#endregion
}

If I implement IEquatableT, will I lose the option to compare by reference?

There are several different things going on here.

The first is that IEquatable<T> is not directly related to the == operator. If you implement IEquatable<T>, but you don't override the == operator, then == will continue to do what it currently does: compare your objects by reference.

IEquatable<T> gives you an Equals(T) method, and that's it. By itself, it doesn't affect Equals(object) (which you also need to implement), or == and !=.

So let's assume that you do overload the == operator, to call our Equals method:

public static bool operator ==(Foo left, Foo right) => Equals(left, right);
public static bool operator !=(Foo left, Foo right) => !Equals(left, right);

This has only changed the == operator between two Foo instances. You can still write:

if ((object)myObject1 == (object)myObject2))

and that will fall back to using object's == method, which compares by reference.

Another way to do this is:

if (ReferenceEquals(myObject1, myObject2))

which just does the same thing.


Also note that it's rare to implement IEquatable<T> for classes: there's really no point. Classes already have an Equals(object) method and a GetHashCode() method which you need to override, and adding an Equals(T) method doesn't give you much.

IEquatable<T> is however useful for structs: they also have an Equals(object) method you need to override, but if you actually call it then you're going to end up boxing, since it accepts object. If you implement IEquatable<T> here then you also get an Equals(T) method, which you can call without boxing anything.


All of that said, I would write your code as it's intended to work in your application, and do any testing-specific stuff in your test project. This means that if your objects should be compared by reference in your code, I wouldn't add anything new to the object itself.

In your test project, you can write your own method to check whether two instances of your object have the same properties (either as a custom bool AreFoosEqual(Foo f1, Foo f2), or as a full-blown IEqualityComparer<Foo> instance). You can then make this do exactly what your tests need, without worrying about breaking your application.

You can also write your test method as a series of assertions, which tells you which property is incorrect, and what the difference is. This can give you richer test output:

public static void AssertFoosEquals(Foo f1, Foo f2)
{
Assert.AreEqual(f1.Foo, f2.Foo, "Foo property mismatch");
Assert.AreEqual(f1.Bar, f2.Bar, "Bar property mismtach");
}

Unreachable code when overriding Object.Equals and implementing IEquatable?

Is it correct, that the Equals(object obj) method below will never be called with an instance of type Entry?

No. Consider:

object entry1 = new Entry(...);
object entry2 = new Entry(...);
bool equal = entry1.Equals(entry2);

The compile-time type of entry2 is object, not Entry, so this will still call Equals(object).

(Note that your GetHashCode implementation is decidedly dodgy, by the way - and you're currently not guarding against nulls anywhere. It doesn't help that we don't know whether Entry is a class or a struct.)



Related Topics



Leave a reply



Submit