Object Equals - whats the basic logic for pure objects or reference types that don't override Equals?

后端 未结 3 619
广开言路
广开言路 2021-02-14 02:03

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 refl

3条回答
  •  广开言路
    2021-02-14 02:35

    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.

提交回复
热议问题