c# - 对象等于 - 不覆盖等于的纯对象或引用类型的基本逻辑是什么?

标签 c# .net

看完我就到了this我没有找到相关答案 - 所以请在阅读整个问题之前不要将其标记为重复。

我一直在使用反射器并查看 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 的实现,但是根据描述,如果两个对象不是同一个实例并且不为空,它将调用 对象。再次使用 Equals 方法,我会进入一个循环(我说的是纯对象)。

当我说纯对象时,我的意思是这样的:

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

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

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

最佳答案

MSDN's page on object.Equals(object)详细介绍了这一点。具体来说,引用类型的默认实现是引用相等性。 “继承者须知”部分的表格是最直接的。

Reference equality; equivalent to calling Object.ReferenceEquals.

MSDN's page on 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;
    }
}

打印下面的输出:

False

False

Bar.Equals() called

False

False

Baz.Equals() called

False

False

Qux.Equals() called

True

False

所以我抄袭了一点 an answer Hans Passant gave about Math.Pow() ...

这是SSCLI2.0中\clr\src\vm\ecall.cpp中的相关代码

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

这是它映射到的\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

我看到了引用比较、空检查、值类型排除、类型匹配检查和按位相等比较。我看不到 Object.Equals(Object) 是如何调用的。我认为 RuntimeHelpers.Equals(object,object) 的文档完全不正确。

关于c# - 对象等于 - 不覆盖等于的纯对象或引用类型的基本逻辑是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26977431/

相关文章:

c# - windows服务的备份算法

c# - 使用 Entity Framework 支持多个数据库服务器

asp.net - 直接使用 HttpContext 是一件坏事吗?

.net - 如何使用 Rx 以非阻塞方式观察值?

c# - 自定义 XML 序列化 - 包括类名

c# - 是什么触发了早期 session 超时

c# - 哪种空间数据结构(算法)最适合(搜索)一组区域(空间数据)?

c# - 使用 C# MVC4 并将 DbContext 类名称与 ConnectionString 名称匹配以使用正确的数据库?

c# - "Processing of the LINQ expression ' GroupByShaperExpression“升级到 ef 核心后出错。5

c# - 在数据库中存储 X509Certificate2