我在阅读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
源代码?
答案 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
。
virtual bool ((Object)this).Equals(Object)
[link to source][__DynamicallyInvokable] public virtual bool Equals(object obj) => RuntimeHelpers.Equals(this, obj);
这是
Object
基类中的实例方法。如上所述,这可以通过调用无法覆盖的RuntimeHelpers
版本来避免无限递归。
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); }
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原语,枚举等等等。
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
。
override bool ((ValueType)this).Equals(Object)
[link to source](未显示)
无论如何,可能会受到JIT拦截。可能会以runtimecallablewrapper.cpp结束。
这里还有更多要讨论的内容。一个主要因素是很多行为受到特殊JIT处理的严重影响或拦截,其中一些可能取决于在运行时遇到的实例是否可能是值类型,或者JIT是否可以将其排除在外。我也不是这些问题的专家,所以请随意评论和/或纠正。如果我对JIT结果有更多细节感兴趣,请告诉我,我可以稍微扩展一下。