下面是带有注释的例子:
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
}
}
你觉得这个怎么样?
一半答案:
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 ([methodimmpl (MethodImplOptions.InternalCall)]),并且没有可用的源。
回到猜测为什么一种情况可以通过比特进行比较,而另一种情况不能(可能是对齐问题?)
错误出现在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
我在http://blogs.msdn.com/xiangfan/archive/2008/09/01/magic-behind-valuetype-equals.aspx找到了答案。
核心部分是CanCompareBits上的源注释,它的ValueType。Equals用于确定是否使用memcmp样式的比较:
CanCompareBits的注释说
如果valuetype不是,则返回true
包含指针,是紧密的
包装”。和fastqualscheck使用
"memcmp"来加速比较。
作者继续阐述了OP所描述的问题:
假设你有一个结构
只包含一个浮点数。会发生什么
如果一个包含+0.0,另一个包含+0.0
包含-0.0 ?他们应该是
相同,但基本的二进制
表现方式不同。如果你
嵌套其他覆盖的结构
Equals方法,即优化
也会失败。