Object Equals - какова основная логика для чистых объектов или ссылочных типов, которые не переопределяют Equals?

Я получил здесь после прочтения этого, и я не нашел соответствующего ответа. Поэтому, пожалуйста, не отмечайте это как дубликат, пока не прочитаете весь вопрос.

Я использовал рефлектор и посмотрел на Object.Equals. Что я увидел:

[__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
public virtual bool Equals(object obj)
{
    return RuntimeHelpers.Equals(this, obj);
}

И RuntimeHelpers.Equals выглядит следующим образом:

// System.Runtime.CompilerServices.RuntimeHelpers
/// <summary>Determines whether the specified <see cref="T:System.Object" /> instances are considered equal.</summary>
/// <returns>true if the <paramref name="o1" /> parameter is the same instance as the <paramref name="o2" /> parameter, or if both are null, or if o1.Equals(o2) returns true; otherwise, false.</returns>
/// <param name="o1">The first object to compare. </param>
/// <param name="o2">The second object to compare. </param>
[SecuritySafeCritical]
[MethodImpl(MethodImplOptions.InternalCall)]
public new static extern bool Equals(object o1, object o2);

Теперь я не вижу реализацию RuntimeHelpers.Equals, но по описанию, если оба объекта не являются одним и тем же экземпляром и не являются нулевыми, он снова вызовет метод Object.Equals, и я попаду в (я говорю о чистых объектах).

Когда я говорю чистые объекты, я имею в виду что-то вроде этого:

object pureObj1 = new object();
object pureObj2 = new object();
bool areEql = pureObj1.Equals(pureObj2);

По документации это должно вызвать Object.Equals и получить recusive stackoverflow. Возможно, документация неверна, и это проверяет ссылочное равенство для базовых объектов, но я хотел быть уверенным.

Нижняя строка:
При сравнении двух чистых объектов (например, не набрасывание строки на объект) с помощью вызова Equals - как он определяет, равны ли они? - Что произойдет, если я не переопределяю метод Equals, и я вызываю Equals на двух объектах?
Постскриптум есть ли вообще что я вижу исходный код RuntimeHelpers.Equals?

Ответ 1

Страница MSDN на object.Equals(object) подробно описывает это. В частности, реализация по умолчанию для ссылочных типов является ссылочным равенством. Таблица в разделе "Примечания для наследователей" является самой прямой.

Reference равенство; эквивалент вызова Object.ReferenceEquals.

Страница MSDN на RuntimeHelpers.Equals(object,object) говорит, что object.Equals(object) вызывается в случае, если его аргументы не являются ссылочными, и ни один из них не равен нулю. Это явно ложно; фактически проявленное поведение состоит в том, что RuntimeHelpers.Equals(object,object) никогда не вызывает object.Equals(object).

Например, этот LINQPad script:

void Main()
{
    object left = new Foo();
    object right = new Foo();
    left.Equals(right).Dump();
    RuntimeHelpers.Equals( left, right ).Dump();
    left = new Bar();
    right = new Bar();
    left.Equals(right).Dump();
    RuntimeHelpers.Equals( left, right ).Dump();
    left = new Baz();
    right = new Baz();
    left.Equals(right).Dump();
    RuntimeHelpers.Equals( left, right ).Dump();
    left = new Qux();
    right = new Qux();
    left.Equals(right).Dump();
    RuntimeHelpers.Equals( left, right ).Dump();
}

private class Foo {}

private class Bar {
    public override bool Equals(object obj) { 
        "Bar.Equals() called".Dump();
        return base.Equals(obj);
    }
}

private class Baz {
    public override bool Equals(object obj) { 
        "Baz.Equals() called".Dump();
        return RuntimeHelpers.Equals( this, obj );
    }
}

private class Qux {
    public override bool Equals(object obj) { 
        "Qux.Equals() called".Dump();
        return true;
    }
}

выводит результат ниже:

False

False

Bar.Equals() называется

False

False

Baz.Equals() называется

False

False

Qux.Equals() называется

True

False

Итак, я немного подрезал из ответ, который Ханс Пассант дал Math.Pow()...

Это соответствующий код из \clr\src\vm\ecall.cpp в

Это код для функции в \clr\src\vm\comobject.cpp, на которую он отображается:

FCIMPL2(FC_BOOL_RET, ObjectNative::Equals, Object *pThisRef, Object *pCompareRef)
{
    CONTRACTL
    {
        THROWS;
        DISABLED(GC_NOTRIGGER);
        INJECT_FAULT(FCThrow(kOutOfMemoryException););
        MODE_COOPERATIVE;
        SO_TOLERANT;          
    }
    CONTRACTL_END;

    if (pThisRef == pCompareRef)    
        FC_RETURN_BOOL(TRUE);

    // Since we are in FCALL, we must handle NULL specially.
    if (pThisRef == NULL || pCompareRef == NULL)
        FC_RETURN_BOOL(FALSE);

    MethodTable *pThisMT = pThisRef->GetMethodTable();

    // If it not a value class, don't compare by value
    if (!pThisMT->IsValueClass())
        FC_RETURN_BOOL(FALSE);

    // Make sure they are the same type.
    if (pThisMT != pCompareRef->GetMethodTable())
        FC_RETURN_BOOL(FALSE);

    // Compare the contents (size - vtable - sink block index).
    BOOL ret = memcmp(
        (void *) (pThisRef+1), 
        (void *) (pCompareRef+1), 
        pThisRef->GetMethodTable()->GetBaseSize() - sizeof(Object) - sizeof(int)) == 0;

    FC_GC_POLL_RET();

    FC_RETURN_BOOL(ret);
}
FCIMPLEND

Я вижу ссылочное сравнение, нулевые проверки, исключение типа значения, проверку соответствия типа и сравнение поразрядного равенства. Я не вижу, как object.Equals(object) когда-либо называется. Я считаю, что документация для RuntimeHelpers.Equals(object,object) просто неверна.

Ответ 2

Object.Equals является виртуальным. Типы переопределяют его, чтобы иметь другое поведение.

Реализация по умолчанию, как вы заметили, вызывает метод MethodImplOptions.InternalCall (т.е. является частью внутренней среды выполнения .NET). Этот метод выполняет ссылочное равенство, непосредственно просматривая ссылку (по существу, это сравнение с указателем на C/С++).

Рекурсии нет.

NB. Документация для ReferenceHelper.Equals гласит:

true, если параметр o1 - это тот же экземпляр, что и параметр o2, или оба они null, или если o1.Equals(o2) возвращает true; в противном случае false.

(Акцент от источника.)

Но это означало бы, что a.Equals(b), где Object.ReferenceEquals(a, b) является ложным и не является null, тогда Object.Equals(object) вызывает ReferenceHelper.Equals(object, object) вызовы Object.Equals(object),.... Это, по-видимому, ошибка документации (поведение во время выполнения не является рекурсивным для типов, которые не переопределяют Equals(object), а затем вызываются для разных объектов, что приводит к результату равенства ссылок false).