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

时间:2014-11-17 16:39:38

标签: 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的实现,但是根据描述,如果两个对象不是同一个实例并且不是null,则会调用{{1} }方法再次进入循环(我正在谈论纯对象)。

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

object.Equals

通过文档,这应该调用object pureObj1 = new object(); object pureObj2 = new object(); bool areEql = pureObj1.Equals(pureObj2); 并获得 recusive stackoverflow 。我想也许文档是错误的,这会检查基本对象的引用相等性 - 但我想确定。

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

3 个答案:

答案 0 :(得分:9)

MSDN's page on object.Equals(object)详细介绍了这一点。具体而言,引用类型的默认实现是引用相等。第34节中的表格;继承人注释&#34;是最直接的。

  

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

MSDN's page on RuntimeHelpers.Equals(object,object)确实说如果Object.Equals(Object)的参数不是引用相等而且都不是null,则会调用RuntimeHelpers.Equals(object,object)。这显然是错误的;实际展示的行为是 Object.Equals(Object)从不致电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; } }

例如,这个LINQPad脚本:

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

打印下面的输出:

  

     

     

Bar.Equals()调用

     

     

     

Baz.Equals()调用

     

     

     

Qux.Equals()调用

     

     

所以我从an answer Hans Passant gave about Math.Pow() ......

中榨了一下

这是SSCLI2.0

中来自\ clr \ src \ vm \ ecall.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

这是映射到它的\ clr \ src \ vm \ comobject.cpp中函数的代码:

Object.Equals(Object)

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

答案 1 :(得分:5)

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

正如您所注意到的,默认实现调用MethodImplOptions.InternalCall方法(即,它是.NET运行时内部的一部分)。此方法通过直接查看引用来执行引用相等(基本上它执行C / 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引用相等结果。)

答案 2 :(得分:0)

我认为此页面的其他地方存在一些混淆。请注意 数字3和4之间存在差异! 。另一个容易出错的问题是base.Equals实例方法(#1)调用RuntimeHelpers.Equals版本,而调用自己的静态方法Object.ReferenceEquals

  1.   

    virtual bool ((Object)this).Equals(Object)
    [link to source]

    [__DynamicallyInvokable]
    public virtual bool Equals(object obj) => RuntimeHelpers.Equals(this, obj);
    
         

    这是Object基类中的实例方法。如上所述,这可以通过调用无法覆盖的RuntimeHelpers版本来避免无限递归。

  2.   

    static bool Object.Equals(Object, Object)
    [link to source]

    public static bool Equals(Object objA, Object objB)
    {
       if (objA == objB)
           return true;
    
       if (objA == null || objB == null)
           return false;
    
       return objA.Equals(objB);
    }
    
  3.   

    static bool Object.ReferenceEquals(Object, Object)
    [link to source]

    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
    [NonVersionable, __DynamicallyInvokable]
    public static bool ReferenceEquals(Object objA, Object objB)
    {
        return objA == objB;
    }
    
         

    结果是最简单的运行时代码。通常最终内联两个引用类型的句柄值的简单CPU比较。不调用用户定义的Equals覆盖,也不尝试以任何方式将非引用类型等同起来。也就是说,没有两种值类型,blittable原语,枚举等等等。

  4.   

    static bool RuntimeHelpers.Equals(Object, Object)
    [link to source]

    [MethodImpl(MethodImplOptions.InternalCall), SecuritySafeCritical]
    public new static extern bool Equals(object o1, object o2);
    
         

    注意extern关键字:没有IL代码;这会直接跳转到CLR内部代码。另请注意,这是一种newslot静态方法,因此您必须在任何调用网站上使用“R̲u̲n̲t̲i̲m̲e̲H̲e̲l̲p̲e̲r̲s̲.Equals”对其进行限定,否则您将获得该实例的非常不同行为方法(#2)Object.Equals

  5.   

    override bool ((ValueType)this).Equals(Object)
    [link to source]

         

    (未显示)
    无论如何,可能会受到JIT拦截。可能会以runtimecallablewrapper.cpp结束。

  6. 这里还有更多要讨论的内容。一个主要因素是很多行为受到特殊JIT处理的严重影响或拦截,其中一些可能取决于在运行时遇到的实例是否可能是值类型,或者JIT是否可以将其排除在外。我也不是这些问题的专家,所以请随意评论和/或纠正。如果我对JIT结果有更多细节感兴趣,请告诉我,我可以稍微扩展一下。