对象等于 - 对于不重写等于的纯对象或引用类型的基本逻辑是什么?

Ami*_*ich 11 .net c#

我在读完这篇文章之后来到这里并且没有找到相关的答案 - 所以在你阅读整个问题之前,请不要将其标记为副本.

我一直在使用反射器并进行调查.Object.Equals我看到的是:

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

而且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);
Run Code Online (Sandbox Code Playgroud)

现在我看不到实现RuntimeHelpers.Equals但是通过描述,如果两个对象不是同一个实例并且不是null,它将object.Equals再次调用该方法,我将进入一个循环(我说的是纯对象).

当我说纯物体时我的意思是这样的:

object pureObj1 = new object();
object pureObj2 = new object();
bool areEql = pureObj1.Equals(pureObj2);
Run Code Online (Sandbox Code Playgroud)

通过文档,这应该调用Object.Equals并获得一个重复的stackoverflow.我想也许文档是错误的,这会检查基本对象的引用相等性 - 但我想确定.

结论:
当通过Equals调用比较两个纯对象(例如,不将字符串转换为对象)时- 它如何确定它们是否相等?- 如果我不覆盖Equals方法并且调用Equals两个对象会发生什么?
无论如何,Ps在那里我可以看到RuntimeHelpers.Equals源代码?

Jud*_*con 10

MSDN的页面object.Equals(object)详细介绍了这一点.具体而言,引用类型的默认实现是引用相等."继承人备注"部分中的表格是最直接的.

参考平等; 相当于调用Object.ReferenceEquals.

MSDN的页面RuntimeHelpers.Equals(object,object)确实说Object.Equals(Object)在它的参数不是引用相等而且都不是null的情况下被调用.这显然是错误的; 实际展现的行为是RuntimeHelpers.Equals(object,object)从不打电话Object.Equals(Object).

例如,这个LINQPad脚本:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

打印下面的输出:

Bar.Equals()调用

Baz.Equals()调用

Qux.Equals()调用

真正

所以我从Hans Passant给出的答案中Math.Pow()略微说道 ......

这是\ CLR\SRC \虚拟机\在ecall.cpp相关的代码SSCLI2.0

FCFuncStart(gObjectFuncs)
    FCIntrinsic("GetType", ObjectNative::GetClass, CORINFO_INTRINSIC_Object_GetType)
    FCFuncElement("InternalGetHashCode", ObjectNative::GetHashCode)
    FCFuncElement("InternalEquals", ObjectNative::Equals)
    FCFuncElement("MemberwiseClone", ObjectNative::Clone)
FCFuncEnd()
Run Code Online (Sandbox Code Playgroud)

这是它映射到的\ 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's 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
Run Code Online (Sandbox Code Playgroud)

我看到参考比较,空检查,值类型排除,类型匹配检查和按位相等比较.我看不出怎么Object.Equals(Object)称呼.我认为文档RuntimeHelpers.Equals(object,object)是完全错误的.


Ric*_*ard 5

Object.Equals虚拟的.类型覆盖它以具有不同的行为.

正如您所注意到的,默认实现调用MethodImplOptions.InternalCall方法(即,它是.NET运行时内部的一部分).此方法通过直接查看引用来执行引用相等(实际上它执行C/C++指针比较).

没有递归.

NB.文档ReferenceHelper.Equals说:

如果O1参数是相同的实例作为O2参数,或者如果两者都为空,或者如果o1.Equals(o2)返回真; 否则,错误.

(来源强调.)

但是这意味着a.Equals(b)哪里Object.ReferenceEquals(a, b)是假的,两者都不是null,那就Object.Equals(object)叫来ReferenceHelper.Equals(object, object)电话Object.Equals(object)...... 这似乎是一个文档错误(对于不覆盖的类型,运行时行为不是递归的Equals(object),然后调用不同的对象导致false引用相等的结果).