1

I would like to compare an object with antoher to know if they are equal or not. So it seems the way to do that is implementing the IEquatable interface in my class.

But I am not sure about how this affect to the behaviour of my class. Now, in my code, I use to compare two object by reference in this way:

if(myObject1 == myObject2)
{
    // code when both objects are the same. 
    // Set values in some properties and do some actions according that.
}
else
{
    // code when both objects are no the same. 
    // Set values in some properties and do some actions according that.
}

But in some special cases, mainly in testing, I would like to compare 2 objects and considerate equal if all the properties are equal, but in this case I don't know if it will affect to my main code, in which I am compare by reference.

Another option could be implement a method that compare in this way, but I don't know if it is a good idea or it is better to implement the IEquatable interface.

Thanks.

Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
Álvaro García
  • 18,114
  • 30
  • 102
  • 193
  • 3
    `if (ReferenceEquals(myObject1, myObject2)) {...}`? – Dmitry Bychenko Feb 27 '20 at 10:16
  • 1
    `==` operator from reference types uses [`ReferenceEquals`](https://learn.microsoft.com/en-us/dotnet/api/system.object.referenceequals?view=netframework-4.8), which is not `virtual` and you can't override it. I guess, [remarks](https://learn.microsoft.com/en-us/dotnet/api/system.object.referenceequals?view=netframework-4.8#remarks) section can answer your question – Pavel Anikhouski Feb 27 '20 at 10:18
  • 1
    Why not create an `IEqualityComparer` for those "special" comparisons? – Corak Feb 27 '20 at 10:20
  • @Corak it seems IEqualityCOmparer is for ordering, https://stackoverflow.com/questions/9316918/what-is-the-difference-between-iequalitycomparert-and-iequatablet – Álvaro García Feb 27 '20 at 10:23
  • @ÁlvaroGarcía `IEqualityComparer` cannot give you ordering. That's `IComparer` you're thinking of – canton7 Feb 27 '20 at 10:25
  • @ÁlvaroGarcía - `IEqualityComparer` is for comparing two T's and see, if they are equal. Sounds like exactly what you want (btw. let the class inherit `EqualityComparer` (without the `I`); makes life easier). -- for _ordering_, you might want `IComparer` which _compares_ two T's to tell if one should come before the other or if they are comparably equal. So an `IComparer` _could_ also be used to determine, if two T's are equal, but with `IEqualityComparer` the intent is much clearer. – Corak Feb 27 '20 at 10:29

3 Answers3

4

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");
}
canton7
  • 37,633
  • 3
  • 64
  • 77
3

If you want to compare same objects but in different ways, I suggest using a comparer which implements IEqualityComparer<T>:

  public class MyClassTestComparer : IEqualityComparer<MyClass> {
    public bool Equals(MyClass x, MyClass y) {
      if (ReferenceEquals(x, y))
        return true;
      else if (null == x || null == y)
        return false;

      return x.Propery1 == y.Property1 && 
             x.Propery2 == y.Property2 &&
             x.ProperyN == y.PropertyN; 
    }

    public int GetHashCode(MyClass obj) {
      return obj == null 
        ? 0 
        : obj.Propery1.GetHashCode() ^ obj.Propery2.GetHashCode();
    }
  }

then you can choose the right comparer

 public static IEqualityComparer<MyClass> MyClassComparer {
   if (we_should_use_test_comparer)
     return new MyClassTestComparer();
   else
     return EqualityComparer<MyClass>.Default;  
 } 

Finally if will be

 if (MyClassComparer.Equals(myObject1, myObject2)) {
   // Equals: by reference or by properties (in test)
 }
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
1

When you make a unit test ->

Like:

public void TestSomething()
{
     var expectedValue1 = "SomeExpectedValue";

     var actualValue = instance.Method();

     Assert.Equal(expectedValue1, actualValue);

}

Then you "simply" assert the properties you want to look at, if you return an object and not a value:

public void TestSomething()
{
     var expectedValue1 = "SomeExpectedValue";

     TestableObject subject = instance.Method();

     Assert.Equal(expectedValue1, subject.Somevalue);
}

If you want a more generic setup, you can write a reflection using generic flow, that looks at all properties on an object and attempts to match them to the another provided object.

Or you could download a nuget package of tools that already allow you to do this.

I would not override any functionality, simply for the purpose of testing. That way lies spaghetti code. Ideally your code should be 100% verifiable by unit tests, without having specific code sections that augment or assist your testing methods. (Unless said code is restricted to the test project itself, and is not contained within any of the actual code being tested.

Morten Bork
  • 1,413
  • 11
  • 23