Search code examples
referencetypesequalsreferenceequals

What is the difference between a==b and a.Equals(b) in the context of value and reference types?


I've come across this question quite a few times, and while the answers make sense, i wanted to check it out myself with a simple console app.

    class Program
{
    static void Main(string[] args)
    {
        // Case 1 : FOR REFERENCE TYPES where class b is a copy of class a
        Class1 a = new Class1("Hello");
        Class1 b = a;
        Console.WriteLine("case 1");
        Console.WriteLine(a.GetHashCode());
        Console.WriteLine(b.GetHashCode());
        Console.WriteLine(a==b); //true
        Console.WriteLine(a.Equals(b)); //true

        // Case 2 : FOR REFERENCE TYPES where class b is not a copy of class a, but it assigned the same values
        // Though the referenced memory addresses are different, the fields of the class are assigned the same values, but will have different hashcodes and are therfore not equal.
        Class1 c = new Class1("Hello");
        Console.WriteLine(" ");
        Console.WriteLine("case 2");
        Console.WriteLine(a.GetHashCode());
        Console.WriteLine(c.GetHashCode());
        Console.WriteLine(a==c);//false
        Console.WriteLine(a.Equals(c));//false

        // Case 3 : FOR REFERENCE TYPES where two strings are assigned the same values, an exception to the way value types behave.
        // using the '==' operstor with strings compares their values not memory addresses.
        string s1 = "hi";
        string s2 = "hi";
        Console.WriteLine(" ");
        Console.WriteLine("case 3");
        Console.WriteLine(s1 == s2);//true
        Console.WriteLine(s1.Equals(s2));//true

        //Case 4 : FOR VALUE TYPES - they are the same when comparing the same type.
        int x = 5;
        int y = 5;
        Console.WriteLine(" ");
        Console.WriteLine("case 4");
        Console.WriteLine(x);
        Console.WriteLine(y);
        Console.WriteLine(x == y);//true
        Console.WriteLine(x.Equals(y));//true

        // Case 5 : Another value type scenario for completeness
        x = y;
        Console.WriteLine(" ");
        Console.WriteLine("case 5");
        Console.WriteLine(x);
        Console.WriteLine(y);
        Console.WriteLine(x.GetType());
        Console.WriteLine(y.GetType());
        Console.WriteLine(x == y);//true
        Console.WriteLine(x.Equals(y));//true

        // Case 6 : Yet Another value type scenario for completeness, with different value types.
        float z = 5;
        Console.WriteLine(" ");
        Console.WriteLine("case 6");
        Console.WriteLine(x.GetType());
        Console.WriteLine(z.GetType());
        Console.WriteLine(x);
        Console.WriteLine(z);
        Console.WriteLine(x == z);//true
        Console.WriteLine(x.Equals(z));//false, as the values being compared are of two different types- int and float. The .Equals method expects them to be the same type.

        // Case 7 : For giggles, Yet Another ref type scenario for completeness, with objects.
        string s3 = new string(new char[] { 'h', 'e', 'l', 'l', 'o' });
        string s4 = new string(new char[] { 'h', 'e', 'l', 'l', 'o' });
        object obj1 = s3;
        object obj2 = s4;
        Console.WriteLine(" ");
        Console.WriteLine("case 7");
        Console.WriteLine(obj1.ToString());
        Console.WriteLine(obj2.ToString());
        Console.WriteLine(obj1.GetHashCode());
        Console.WriteLine(obj2.GetHashCode());
        Console.WriteLine(obj1 == obj2);//false - as they refer to different addresses.
        Console.WriteLine(obj1.Equals(obj2));//true - in this case both objects have the same hashcode.
        Console.ReadKey();

    }

    public class Class1
    {
            string name;
            public Class1(string strName)
            {
                name = strName;
            }
     }

}

Assumption 1 : What i understood from the commonly posted replies was that for reference types, a==b compares references , whereas a.Equals(b) compares the actual values referenced. This is what threw me off when viewing the results of my program.

With reference to my program, In Case 2 - Though the referenced memory addresses for a and c are different, their fields are assigned the same values. Still a.Equals(c) returns false as they still are not equal as they have different hashcodes. I had assumed they would return true initially based on Assumption 1, but it makes sense they are not equal. But then what really is the exact difference between == and .Equals?

In case 3 , using the '==' operator with strings compares their values not memory addresses.

In case 6, the value types being compared by the .Equals method are different, whereas the method expects them to be of the same type. Hence it returns false.

What I still don't understand is case 7. Why do the objects have the same hashcode in this case? Sorry for the lengthy code and thanks in advance!


Solution

  • I think a key thing to remember is that with a == b the compiler determines what function to call at compile-time by looking at the types of a and b. If a and b are both of type object, the compiler will generate a call to ReferenceEquals, whereas if they are both of type string the compiler will generate a call to string.Equals.

    When the compiler sees a.Equals(b), however, it generates a call to the a's Equals method, which is determined at runtime because it's a virtual method. If a is a string then it will call the overload that does a string comparison (as you expect). If a is some other object then it will call the overloaded Equals method on that object if it has one, or object.Equals (which does a ReferenceEquals comparison) if it doesn't.