下面是带有注释的例子:

class Program
{
    // first version of structure
    public struct D1
    {
        public double d;
        public int f;
    }

    // during some changes in code then we got D2 from D1
    // Field f type became double while it was int before
    public struct D2 
    {
        public double d;
        public double f;
    }

    static void Main(string[] args)
    {
        // Scenario with the first version
        D1 a = new D1();
        D1 b = new D1();
        a.f = b.f = 1;
        a.d = 0.0;
        b.d = -0.0;
        bool r1 = a.Equals(b); // gives true, all is ok

        // The same scenario with the new one
        D2 c = new D2();
        D2 d = new D2();
        c.f = d.f = 1;
        c.d = 0.0;
        d.d = -0.0;
        bool r2 = c.Equals(d); // false! this is not the expected result        
    }
}

你觉得这个怎么样?


当前回答

错误出现在System的下面两行。ValueType:(我进入了引用源)

if (CanCompareBits(this)) 
    return FastEqualsCheck(thisObj, obj);

(这两个方法都是[methodimmpl (MethodImplOptions.InternalCall)])

当所有字段都是8字节宽时,CanCompareBits错误地返回true,导致两个不同但语义相同的值按位比较。

当至少有一个字段不是8字节宽时,CanCompareBits返回false,代码继续使用反射遍历字段并为每个值调用Equals,这将正确地将-0.0视为等于0.0。

以下是来自SSCLI的CanCompareBits源代码:

FCIMPL1(FC_BOOL_RET, ValueTypeHelper::CanCompareBits, Object* obj)
{
    WRAPPER_CONTRACT;
    STATIC_CONTRACT_SO_TOLERANT;

    _ASSERTE(obj != NULL);
    MethodTable* mt = obj->GetMethodTable();
    FC_RETURN_BOOL(!mt->ContainsPointers() && !mt->IsNotTightlyPacked());
}
FCIMPLEND

其他回答

错误出现在System的下面两行。ValueType:(我进入了引用源)

if (CanCompareBits(this)) 
    return FastEqualsCheck(thisObj, obj);

(这两个方法都是[methodimmpl (MethodImplOptions.InternalCall)])

当所有字段都是8字节宽时,CanCompareBits错误地返回true,导致两个不同但语义相同的值按位比较。

当至少有一个字段不是8字节宽时,CanCompareBits返回false,代码继续使用反射遍历字段并为每个值调用Equals,这将正确地将-0.0视为等于0.0。

以下是来自SSCLI的CanCompareBits源代码:

FCIMPL1(FC_BOOL_RET, ValueTypeHelper::CanCompareBits, Object* obj)
{
    WRAPPER_CONTRACT;
    STATIC_CONTRACT_SO_TOLERANT;

    _ASSERTE(obj != NULL);
    MethodTable* mt = obj->GetMethodTable();
    FC_RETURN_BOOL(!mt->ContainsPointers() && !mt->IsNotTightlyPacked());
}
FCIMPLEND

你觉得这个怎么样?

总是重写值类型上的Equals和GetHashCode。它将是快速和正确的。

这对我来说是真的,Mono的gmcs 2.4.2.3。

只是一个10年前的bug的更新:它已经在。net Core中被修复了(免责声明:我是这篇PR的作者),这可能会在。net Core 2.1.0中发布。

这篇博客文章解释了这个错误,以及我是如何修复它的。

维尔克斯的猜想是正确的。“CanCompareBits”所做的是检查所讨论的值类型是否在内存中“紧密地打包”。一个紧凑的结构是通过简单地比较组成结构的二进制位来比较的;松散包装的结构通过对所有成员调用Equals来进行比较。

这解释了SLaks的观察,即它用全是双精度的结构体进行还原;这样的结构总是紧凑的。

不幸的是,正如我们在这里看到的,这引入了语义上的差异,因为双精度对象的位比较和双精度对象的等号比较给出了不同的结果。