Frage

In this following example the third evaluation returns false, all good, but the fourth example returns true..
I don't quite understand how this works however, by default Object.Equals compares two references for object equality, and seeing as a and b both point to a unique instance of a string, this should return false, which it does in the third example but not in the fourth.
Now I understand why it returns true in the 2nd example seeing as the .Equals() method is overridden in the string class, but in the fourth example we're casting this string as an object.
So wouldn't it call Object.Equals in this case?

static void Main()
{
    // Create two equal but distinct strings
    string a = new string(new char[] {'h', 'e', 'l', 'l', 'o'});
    string b = new string(new char[] {'h', 'e', 'l', 'l', 'o'});

    Console.WriteLine (a == b); // Returns true
    Console.WriteLine (a.Equals(b)); // Returns true

    // Now let's see what happens with the same tests but
    // with variables of type object
    object c = a;
    object d = b;

    Console.WriteLine (c == d); // Returns false
    Console.WriteLine (c.Equals(d)); // Returns true
}
War es hilfreich?

Lösung

The clue is the words "by default". string overrides object.Equals with a custom implementation. Since object.Equals is a polymorphic method (virtual / override / etc), the most-derived implementation gets used even if the variable (/expression) type is object.

==, however, is not polymorphic; the implementation used depends entirely on the variable (/expression) type. In this case, since the known type is object, the only comparison available is reference equality.

Perhaps more succinctly:

class Foo {
    public override string ToString() { return "hi"; }
}
//...
object obj = new Foo();
string s = obj.ToString(); // this is "hi"

This is the same principle: the most derived overload of a virtual method is used, regardless of the type that the compiler knows about (object in this case).

Andere Tipps

The Equals method is virtual, that means that even if it is called on an reference with the object type, it will end up invoking the implementation for the concrete type of the instance.

From the generated IL: a.Equals(b) becomes

IL_003C:  ldloc.0     // a
IL_003D:  ldloc.1     // b
IL_003E:  callvirt    System.String.Equals

and c.Equals(d) becomes

IL_0057:  ldloc.2     // c
IL_0058:  ldloc.3     // d
IL_0059:  callvirt    System.Object.Equals

So, both are virtual calls on a reference of type string and object, respectively, and both invoke the Equals method, not on the reference, but on the instance itself.

On the other hand, a==b becomes a call of the static method System.String.op_Equality

IL_002F:  ldloc.0     // a
IL_0030:  ldloc.1     // b
IL_0031:  call        System.String.op_Equality

while c==d becomes just

IL_004D:  ldloc.2     // c
IL_004E:  ldloc.3     // d
IL_004F:  ceq      

, a call to the check-equal IL instruction, that does just simple reference checking.


You can also verify that the overriden implementation is called with code like:

class MyClass
{
  public override bool Equals(object obj)
  {
      Console.WriteLine("My Class Equals is called");
      return true;
  }
}

void Main()
{
   object a = new MyClass();
   object b = new MyClass();
   Console.WriteLine (a.Equals(b));
}

This will output

My Class Equals is called
True

Since Equals is a virtual method, any class that implements equals will automatically override the original equals no matter what. If you want to use the object.Equals, you have to use object.ReferenceEquals(a,b).

For more info look into how virtual methods work, and if you're feeling up to it, how vtables are actually implemented (which is actually quite easy once you get the hang of it)

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top