I got here after reading this and I didn't find a relevant answer - So please don't mark this as a duplicate until you read the whole question.
I've been using a reflector and looked into Object.Equals
.What I saw is:
[__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
public virtual bool Equals(object obj)
{
return RuntimeHelpers.Equals(this, obj);
}
And RuntimeHelpers.Equals
looks like this:
// 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);
Now I can't see the implementation of RuntimeHelpers.Equals
but by the description, if both objects aren't the same instance and aren't null it will call the object.Equals
method again and I'd get into a loop (I'm talking about pure objects).
When I say pure objects I mean something like this:
object pureObj1 = new object();
object pureObj2 = new object();
bool areEql = pureObj1.Equals(pureObj2);
By documentation this should call Object.Equals
and get a recusive stackoverflow. I guess maybe the documentation is wrong and this checks reference equality for basic objects - but I wanted to be sure.
Bottom line:
When comparing two pure objects(e.g. not casting a string into on object) via an Equals
call - how does it determine if they are equal? - What happens if I don't override the Equals
method and I call Equals
on two objects?
P.s. is there anyway that I can see the RuntimeHelpers.Equals
source code?
The equals method for class Object implements the most discriminating possible equivalence relation on objects; that is, for any non-null reference values x and y , this method returns true if and only if x and y refer to the same object ( x == y has the value true ).
Every Object in Java includes an equals() and a hashcode() method, but they must be overridden to work properly. To understand how overriding works with equals() and hashcode() , we can study their implementation in the core Java classes. Below is the equals() method in the Object class.
We can override the equals method in our class to check whether two objects have same data or not.
You can override the equals method on a record, if you want a behavior other than the default. But if you do override equals , be sure to override hashCode for consistent logic, as you would for a conventional Java class.
MSDN's page on object.Equals(object)
covers this in some detail. Specifically, the default implementation for reference types is reference equality. The table in the section "Notes for Inheritors" is the most direct.
Reference equality; equivalent to calling Object.ReferenceEquals.
MSDN's page on RuntimeHelpers.Equals(object,object)
does say that Object.Equals(Object)
is called in the case that its arguments are not reference equal and neither is null. This is demonstrably false; the behavior actually exhibited is that RuntimeHelpers.Equals(object,object)
never calls Object.Equals(Object)
.
For example, this LINQPad script:
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;
}
}
prints the output below:
False
False
Bar.Equals() called
False
False
Baz.Equals() called
False
False
Qux.Equals() called
True
False
So I cribbed a little from an answer Hans Passant gave about Math.Pow()
...
This is the relevant code from \clr\src\vm\ecall.cpp in 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()
This is the code for the function in \clr\src\vm\comobject.cpp to which it is mapped:
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
I see the reference comparison, null checks, value type exclusion, type match check, and a bitwise equality comparison. I don't see how Object.Equals(Object)
is ever called. I believe that the documentation for RuntimeHelpers.Equals(object,object)
is simply incorrect.
Object.Equals
is virtual. Types override it to have different behaviour.
The default implementation, as you note, calls to an MethodImplOptions.InternalCall
method (ie. it is part of the .NET runtime's internals). This method performs reference equality by directly looking at the reference (essentially it does a C/C++ pointer comparison).
There is no recursion.
NB. The documentation for ReferenceHelper.Equals
says:
true if the o1 parameter is the same instance as the o2 parameter, or if both are null, or if
o1.Equals(o2)
returns true; otherwise, false.
(Emphasis from the source.)
But this would imply that a.Equals(b)
where Object.ReferenceEquals(a, b)
is false and neither are null
, then Object.Equals(object)
calls ReferenceHelper.Equals(object, object)
calls Object.Equals(object)
, …. This seems to be a documentation error (runtime behaviour is not recursive for types not overriding Equals(object)
and then called for different objects resulting in a false
reference equality result).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With