Переопределение Equals и приведение типов

В следующем примере третья оценка возвращает false, все хорошо, но четвертый пример возвращает true..
Однако я не совсем понимаю, как это работает, по умолчанию Object.Equals сравнивает две ссылки на предмет равенства объектов и видит как a и b оба указывают на уникальный экземпляр строки, это должно возвращать false, что и происходит в третьем примере, но не в четвертом.
Теперь я понимаю, почему он возвращает true во втором примере, поскольку метод .Equals() переопределен. в классе string, но в четвертом примере мы приводим эту строку как объект.
Так не будет ли в данном случае вызываться Object.Equals?

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
}

person Overly Excessive    schedule 23.04.2014    source источник


Ответы (3)


Подсказка - слова "по умолчанию". string переопределяет object.Equals пользовательской реализацией. Поскольку object.Equals является полиморфным методом (virtual / override / и т. д.), используется наиболее производная реализация, даже если тип переменной (/выражения) равен object.

==, однако, не полиморфен; используемая реализация полностью зависит от типа переменной (/выражения). В этом случае, поскольку известен тип object, единственным доступным сравнением является равенство ссылок.

Возможно, более кратко:

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

Это тот же принцип: используется наиболее производная перегрузка виртуального метода, независимо от типа, о котором знает компилятор (в данном случае object).

person Marc Gravell    schedule 23.04.2014
comment
Так, даже если мы приведем строку к объекту, она все равно вызовет переопределенную реализацию .Equals? - person Overly Excessive; 23.04.2014
comment
@OverlyExcessive да; это ключевой момент полиморфизма; акцент: такие операторы, как ==, не полиморфны - person Marc Gravell; 23.04.2014

Метод Equals является виртуальным, это означает, что даже если он вызывается для ссылки с типом object, он в конечном итоге вызовет реализацию для конкретного типа экземпляра.

Из сгенерированного IL: a.Equals(b) становится

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

и c.Equals(d) становится

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

Таким образом, оба являются виртуальными вызовами ссылки типа string и object соответственно, и оба вызывают метод Equals не для ссылки, а для самого экземпляра.

С другой стороны, a==b становится вызовом статического метода System.String.op_Equality

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

в то время как c==d становится просто

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

, вызов инструкции check-equal IL, выполняющей простую проверку ссылок.


Вы также можете убедиться, что переопределенная реализация вызывается с помощью такого кода:

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));
}

Это выведет

My Class Equals is called
True
person SWeko    schedule 23.04.2014

Поскольку Equals является виртуальным методом, любой класс, реализующий equals, автоматически переопределит исходный метод equals, несмотря ни на что. Если вы хотите использовать object.Equals, вы должны использовать object.ReferenceEquals(a,b).

Для получения дополнительной информации посмотрите, как работают виртуальные методы, и, если вы чувствуете, как на самом деле реализованы vtables (что на самом деле довольно просто, когда вы освоитесь)

person Dan    schedule 23.04.2014