Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Equality in Reference Types

Tags:

c#

I am a little confused on content equality in reference types specifically. I am not overriding Equality in either case - so why is the behavior different.

See 2 simple code examples:

Example 1: Returns True

class Program
{
    static void Main(string[] args)
    {
        object o1 = "ABC";
        object o2 = "ABC";

        Console.WriteLine("object1 and object2: {0}", o1.Equals(o2));
    }
}

Example 2: Both statements return False

class Program
{
    static void Main(string[] args)
    {
        Person person1 = new Person("John");
        Person person2 = new Person("John");

        Console.WriteLine("person1 and person2: {0}", person1.Equals(person2));
        Console.WriteLine("person1 and person2: {0}", ((object)person1).Equals((object)person2));

        Console.ReadLine();
    }
}

public class Person
{
    private string personName;

    public Person(string name)
    {
        this.personName = name;
    }
}
like image 274
Patrick Avatar asked Aug 14 '13 16:08

Patrick


1 Answers

There are two effects at work here:

  • String interning means that actually even if you perform a reference equality check, you'll still see True. You can fix that like this:

    object o1 = new StringBuilder("ABC").ToString();
    object o2 = new StringBuilder("ABC").ToString();
    
  • System.String overrides the Equals method to compare the contents of the strings:

    This method performs an ordinal (case-sensitive and culture-insensitive) comparison.

    You can see the difference here:

    object o1 = new StringBuilder("ABC").ToString();
    object o2 = new StringBuilder("ABC").ToString();
    Console.WriteLine(o1.Equals(o2)); // Prints True due to overriding
    Console.WriteLine(ReferenceEquals(o1, o2)); // Prints False
    

Your class doesn't override Equals, so you're getting the default implementation in Object, which is to compare references:

If the current instance is a reference type, the Equals(Object) method tests for reference equality, and a call to the Equals(Object) method is equivalent to a call to the ReferenceEquals method.

You could fix that reasonably easily by overriding Equals:

// It's easier to implement equality correctly on sealed classes
public sealed class Person
{
    private readonly string personName;

    public Person(string name)
    {
        if (name == null)
        {
            throw new ArgumentNullException("name");
        }
        this.personName = name;
    }

    public override bool Equals(object other)
    {
        Person person = other as Person;
        return person != null && person.personName.Equals(personName);
    }

    // Must override GetHashCode at the same time...
    public override int GetHashCode()
    {
        // Just delegate to the name here - it's the only thing we're
        // using in the equality check
        return personName.GetHashCode();
    }
}

Note that in the Equals implementation we could have used:

return person != null && person.personName == personName;

... because string also overloads the == operator. But that's a different matter :)

like image 182
Jon Skeet Avatar answered Nov 19 '22 19:11

Jon Skeet