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

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

我一直在使用reflection器并查看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 /// Determines whether the specified  instances are considered equal. /// true if the  parameter is the same instance as the  parameter, or if both are null, or if o1.Equals(o2) returns true; otherwise, false. /// The first object to compare.  /// The second object to compare.  [SecuritySafeCritical] [MethodImpl(MethodImplOptions.InternalCall)] public new static extern bool Equals(object o1, object o2); 

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

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

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

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

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

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

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

关于RuntimeHelpers.Equals(object,object) MSDN页面确实说过,在它的参数不是引用相等而且都不是null的情况下调用Object.Equals(Object) 。 这显然是错误的; 实际展示的行为是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; } } 

打印下面的输出:

Bar.Equals()调用

Baz.Equals()调用

Qux.Equals()调用

真正

所以我从Hans Passant给出的关于Math.Pow()的回答中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)的文档是完全错误的。

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

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

没有递归。

NB。 ReferenceHelper.Equals的文档说:

如果o1参数与o2参数的实例相同,或者两者都为null ,或者o1.Equals(o2)返回true,则返回true; o1.Equals(o2)返回0 否则,是的。

(来自消息来源。)

但这意味着a.Equals(b)其中Object.ReferenceEquals(a, b)为false且既不为null ,则Object.Equals(object)调用ReferenceHelper.Equals(object, object)调用Object.Equals(object) ,…… 这似乎是一个文档错误(对于不覆盖Equals(object)类型,运行时行为不是递归的,然后调用不同的对象导致false引用相等结果)。

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

  1. virtual bool ((Object) this.Equals (Object)
    [链接到源]

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

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

  2. static bool Object.Equals (Object,Object)
    [链接到源]

     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)
    [链接到源]

     [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)
    [链接到源]

     [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)
    [链接到源]

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

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