简体   繁体   English

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

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

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: 我一直在使用反射器并查看了Object.Equals我看到的是:

[__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: 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);

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

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 . 通过文档,这应该调用Object.Equals并获得一个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(eg not casting a string into on object) via an Equals call - how does it determine if they are equal? 通过Equals调用比较两个纯对象(例如,不将字符串转换为对象) - 它如何确定它们是否相等? - What happens if I don't override the Equals method and I call Equals on two objects? - 如果我不重写Equals方法并在两个对象上调用Equals会发生什么?
Ps is there anyway that I can see the RuntimeHelpers.Equals source code? 无论如何,Ps在那里我可以看到RuntimeHelpers.Equals源代码?

MSDN's page on object.Equals(object) covers this in some detail. 关于object.Equals(object) MSDN页面详细介绍了这一点。 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. 相当于调用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. 关于RuntimeHelpers.Equals(object,object) MSDN页面确实说如果Object.Equals(Object)的参数不是引用相等而且都不为null,则会调用Object.Equals(Object) This is demonstrably false; 这显然是错误的; the behavior actually exhibited is that RuntimeHelpers.Equals(object,object) never calls Object.Equals(Object) . 实际展示的行为是RuntimeHelpers.Equals(object,object)从不调用Object.Equals(Object)

For example, this LINQPad script: 例如,这个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;
    }
}

prints the output below: 打印下面的输出:

False

False

Bar.Equals() called Bar.Equals()调用

False

False

Baz.Equals() called Baz.Equals()调用

False

False

Qux.Equals() called Qux.Equals()调用

True 真正

False

So I cribbed a little from an answer Hans Passant gave about Math.Pow() ... 所以我从Hans Passant给出的关于Math.Pow()的回答中Math.Pow()

This is the relevant code from \\clr\\src\\vm\\ecall.cpp in SSCLI2.0 这是\\ CLR \\ SRC \\虚拟机\\在ecall.cpp相关的代码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: 这是它映射到的\\ 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

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. 我没有看到如何调用Object.Equals(Object) I believe that the documentation for RuntimeHelpers.Equals(object,object) is simply incorrect. 我相信RuntimeHelpers.Equals(object,object)的文档是完全错误的。

Object.Equals is virtual . Object.Equals虚拟的 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). 正如您所注意到的,默认实现调用MethodImplOptions.InternalCall方法(即,它是.NET运行时内部的一部分)。 This method performs reference equality by directly looking at the reference (essentially it does a C/C++ pointer comparison). 此方法通过直接查看引用来执行引用相等(实际上它执行C / C ++指针比较)。

There is no recursion. 没有递归。

NB. NB。 The documentation for ReferenceHelper.Equals says: ReferenceHelper.Equals的文档说:

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; 如果O1参数是相同的实例作为O2参数,或者如果两者都为空 ,或者如果o1.Equals(o2)返回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) , …. 但这意味着a.Equals(b)其中Object.ReferenceEquals(a, b)为false且既不为null ,则Object.Equals(object)调用ReferenceHelper.Equals(object, object)调用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). 这似乎是一个文档错误(对于不覆盖Equals(object)类型,运行时行为不是递归的,然后调用不同的对象导致false引用相等结果)。

I think there is some confusion elsewhere on this page. 我认为这个页面的其他地方有一些混乱。 Please notice that there's a difference between numbers 3 and 4! 请注意, 数字3和4之间存在差异! . Another point that is easily mistaken is that the base.Equals instance method (#1) calls RuntimeHelpers.Equals version, and not its own static method Object.ReferenceEquals . 另一个容易base.Equals点是base.Equals实例方法(#1)调用RuntimeHelpers.Equals版本,而不是它自己的静态方法Object.ReferenceEquals

  1. virtual bool ((Object)this).Equals (Object) 虚拟BOOL((对象) ).Equals(对象)
    [link to source] [链接到来源]

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

    This is the instance method in the Object base class. 这是Object基类中的实例方法。 As noted above, this avoids infinite recursion by calling the RuntimeHelpers version which cannot be overridden. 如上所述,这可以通过调用无法覆盖的RuntimeHelpers版本来避免无限递归。

  2. static bool Object.Equals (Object, Object) 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) 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; } 

    Results in the simplest runtime code. 结果是最简单的运行时代码。 Usually ends up inlined with a trivial CPU comparison of the handle values for the two reference types. 通常最终内联两个引用类型的句柄值的简单CPU比较。 Does not call user-defined Equals overrides and makes no attempt to equate non reference-types by any means. 不调用用户定义的Equals覆盖,也不试图以任何方式将非引用类型等同起来。 That is, no two value types, blittable primitives, enums, etc. will ever equate. 也就是说,没有两种值类型,blittable原语,枚举等等等。

  4. static bool RuntimeHelpers.Equals (Object, Object) static bool RuntimeHelpers.Equals (Object,Object)
    [link to source] [链接到来源]

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

    Notice the extern keyword: there is no IL code; 注意extern关键字:没有IL代码; this jumps directly to a CLR-internal code. 这会直接跳转到CLR内部代码。 Also beware that this is a newslot static method, so you must qualify this with " R̲u̲n̲t̲i̲m̲e̲H̲e̲l̲p̲e̲r̲s̲.Equals " at any call site or you'll get the very different behavior of the instance method (#2) Object.Equals . 还要注意这是一个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) 覆盖布尔((值类型) 这个 ).Equals(对象)
    [link to source] [链接到来源]

    (Not shown) (未显示)
    Probably subject to JIT interception anyway. 无论如何,可能会受到JIT拦截。 May end up at runtimecallablewrapper.cpp . 可能会在runtimecallablewrapper.cpp结束。

There's much more to discuss here. 这里还有更多要讨论的内容。 A major factor is that a lot of the behavior is heavily influenced or intercepted by special JIT processing some of which might depend on whether an instance encountered at runtime can possibly be a value type, or whether the JIT can rule that out. 一个主要因素是很多行为受到特殊JIT处理的严重影响或拦截,其中一些可能取决于在运行时遇到的实例是否可能是值类型,或者JIT是否可以将其排除在外。 I'm no expert on these matters either, so feel free to comment and/or correct. 我也不是这些问题的专家,所以请随意评论和/或纠正。 Let me know if there's interest in more detail on the JIT results, and I might be able to expand this a bit. 让我知道是否有兴趣了解JIT结果的更多细节,我可能会稍微扩展一下。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM