2

I'm trying to implement the IEquatable<T> interface and == operator. The implementation below triggers a NullReferenceException when I try to use the == operator despite the fact that both operands are non-null. I've added comments to the minimum example code to show exactly where the exception occurs. What am I doing wrong?

using System;

namespace scratchpad
{
    class TestClass : IEquatable<TestClass>
    {
        public int data;
        public TestClass(int d)
        {
            this.data = d;
        }
        public bool Equals(TestClass other)
        {
            if (other == null)
                return false;
            else
                return this.data == other.data;
        }

        public override bool Equals(object other)
        {
            if (other is TestClass)
                return this.Equals((TestClass)other);
            else //Includes null
                return false;
        }

        public override int GetHashCode()
        {
            return this.data;
        }

        public static bool operator ==(TestClass left, TestClass right)
        {
            return left.Equals(right); //This line triggers the NullReferenceException
        }

        public static bool operator !=(TestClass left, TestClass right)
        {
            return !left.Equals(right);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            TestClass tc1 = new TestClass(10);
            TestClass tc2 = new TestClass(10);
            Console.WriteLine("tc1="+tc1.data); //Prints "tc1.data=10" fine
            Console.WriteLine("tc1="+tc1.data); //Prints "tc2.data=10" fine
            bool isEqual = tc1 == tc2; //NullReferenceException occur here
            Console.WriteLine("isEqual="+isEqual); //Never gets to here
        }
    }
}

Edit (to clarify question in response to duplicates question flags): I am not asking what a NullReferenceException is (I understand that) and I am not interested in ReferenceEquals as I need to equate the object's values.

Verwirrt
  • 349
  • 1
  • 12
  • Possible duplicate of [What is a NullReferenceException, and how do I fix it?](https://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-do-i-fix-it) – Stefan Sep 22 '18 at 15:15
  • 1
    What does the debugger say? – Stefan Sep 22 '18 at 15:16
  • Possible duplicate of [How do I check for nulls in an '==' operator overload without infinite recursion?](https://stackoverflow.com/questions/73713/how-do-i-check-for-nulls-in-an-operator-overload-without-infinite-recursion) – alxnull Sep 22 '18 at 15:33

2 Answers2

4

The line other == null in Equals(TestClass) calls the equality operator, which calls Equals(TestClass) -- an infinite loop. On the second round, other is null, which leads to a NullReferenceException when you pass that to the equality operator as the left parameter.

You should use ReferenceEquals(other, null) or other is null instead.

jan.h
  • 524
  • 5
  • 11
  • This is useful for comparing whether two references 'point' to the same object, and might help others, but I am (was) interested in equating object values so I've accepted FlashOver's answer. – Verwirrt Sep 22 '18 at 16:23
3

Actually, your overloaded equality operator is hit three times:

First, when called from Program.Main(string[]) with the line tc1 == tc2, where left=tc1 and right=tc2, which then calls TestClass.Equals(TestClass) where other=tc2.

From there, other == null now calls your overloaded equality operator a second time, where left=tc2 and right=null. Now, TestClass.Equals(TestClass) is called also a second time, where other=null.

And finally, other == null calls your overloaded equality operator for a third time, where both left=null and right=null. This now eventually causes the System.NullReferenceException because left was null.

To fix this coding error, replace other == null with other is null in TestClass.Equals(TestClass):

public bool Equals(TestClass other)
{
    if (other is null)
        return false;
    else
        return data == other.data;
}

Alternatively, as conditional expression (using expression body):

public bool Equals(TestClass other) => !(other is null) && data == other.data;
FlashOver
  • 1,099
  • 1
  • 7
  • 17