这是带注释的示例:
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
}
}
那么,你怎么看待这个?
答案 0 :(得分:383)
该错误出现在以下两行System.ValueType
中:(我进入了参考源)
if (CanCompareBits(this))
return FastEqualsCheck(thisObj, obj);
(两种方法都是[MethodImpl(MethodImplOptions.InternalCall)]
)
当所有字段都是8字节宽时,CanCompareBits
错误地返回true,从而对两个不同但语义相同的值进行逐位比较。
当至少一个字段不是8字节宽时,CanCompareBits
返回false,代码继续使用反射循环遍历字段并为每个值调用Equals
,这正确地处理{{ 1}}等于-0.0
。
以下是SSCLI 0.0
的来源:
CanCompareBits
答案 1 :(得分:59)
我在http://blogs.msdn.com/xiangfan/archive/2008/09/01/magic-behind-valuetype-equals.aspx找到答案。
核心部分是CanCompareBits
上的来源评论,ValueType.Equals
用于确定是否使用memcmp
- 样式比较:
CanCompareBits的评论说 “如果值类型没有,则返回true 包含指针并且紧密 打包“。和FastEqualsCheck一起使用 “memcmp”加快比较。
作者继续说明OP所描述的问题:
想象一下你有一个结构 只包含一个浮点数。会发生什么 如果一个包含+0.0,另一个包含+0.0 包含-0.0?他们应该是 相同,但底层二进制 代表性不同。如果你 嵌套覆盖其他结构 Equals方法,即优化 也会失败。
答案 2 :(得分:52)
Vilx的猜想是正确的。 “CanCompareBits”所做的是检查所讨论的值类型是否在内存中“紧密包装”。通过简单地比较构成结构的二进制位来比较紧密压缩的结构;通过在所有成员上调用Equals来比较松散堆积的结构。
这解释了SLaks的观察,即它的结构都是双重的结构;这些结构总是紧密包装。
不幸的是,正如我们在这里看到的那样,这引入了语义差异,因为双精度的按位比较和双精度的等于比较给出了不同的结果。
答案 3 :(得分:22)
半个回答:
Reflector告诉我们ValueType.Equals()
做了类似的事情:
if (CanCompareBits(this))
return FastEqualsCheck(this, obj);
else
// Use reflection to step through each member and call .Equals() on each one.
不幸的是,CanCompareBits()
和FastEquals()
(两种静态方法)都是extern([MethodImpl(MethodImplOptions.InternalCall)]
)并且没有可用的源。
回到猜测为什么一个案例可以按位进行比较,而另一个案例不能(对齐问题可能?)
答案 4 :(得分:17)
使用Mono的gmcs 2.4.2.3, 对我来说是真实的。
答案 5 :(得分:14)
更简单的测试用例:
Console.WriteLine("Good: " + new Good().Equals(new Good { d = -.0 }));
Console.WriteLine("Bad: " + new Bad().Equals(new Bad { d = -.0 }));
public struct Good {
public double d;
public int f;
}
public struct Bad {
public double d;
}
编辑:浮动也会发生错误,但只有在结构中的字段加起来为8个字节的倍数时才会发生。
答案 6 :(得分:10)
它必须与逐位比较相关,因为0.0
应仅与信号位-0.0
不同。
答案 7 :(得分:5)
......你怎么看待这个?
始终在值类型上重写Equals和GetHashCode。这将是快速和正确的。
答案 8 :(得分:4)
这个10年前的错误只是一个更新:它可能在.NET中发布的has been fixed(免责声明:我是此PR的作者)核心2.1.0。
blog post解释了这个错误以及我如何解决它。
答案 9 :(得分:2)
如果你像这样制作D2
public struct D2
{
public double d;
public double f;
public string s;
}
这是真的。
如果你这样做
public struct D2
{
public double d;
public double f;
public double u;
}
它仍然是假的。
如果结构只保存双打,那么我 t似乎是假的。
答案 10 :(得分:1)
它必须与零相关,因为更改行
d.d = -0.0
为:
d.d = 0.0
导致比较成立......