任何人都可以解释这个奇怪的行为,在C#签署花车?
这里有一个评论的例子:
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(); af = bf = 1; ad = 0.0; bd = -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(); cf = df = 1; cd = 0.0; dd = -0.0; bool r2 = c.Equals(d); // false! this is not the expected result } }
所以,对于这个你有什么想法?
该错误是在System.ValueType
的以下两行:(我走进参考源)
if (CanCompareBits(this)) return FastEqualsCheck(thisObj, obj);
(两种方法都是[MethodImpl(MethodImplOptions.InternalCall)]
)
当所有字段的宽度都是8个字节时, CanCompareBits
错误地返回true,导致两个不同但语义相同的值按位比较。
当至less有一个字段不是8字节宽时, CanCompareBits
返回false,代码继续使用reflection来遍历字段并为每个值调用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.aspxfind了答案。;
核心部分是CanCompareBits
的源代码注释, ValueType.Equals
用来决定是否使用memcmp
-style比较:
CanCompareBits的注释说:“如果value属性不包含指针并且被紧密包装,则返回true”。 FastEqualsCheck使用“memcmp”来加速比较。
作者继续说明OP所描述的问题:
想象一下你有一个只包含一个浮点的结构。 如果一个包含+0.0,另一个包含-0.0,会发生什么? 它们应该是相同的,但是二进制表示的底层是不同的。 如果你嵌套其他结构覆盖等于方法,那么优化也将失败。
Vilx的猜想是正确的。 “CanCompareBits”所做的是检查问题的值types是否在内存中“紧密堆栈”。 通过比较构成结构的二进制位来比较紧密排列的结构; 通过在所有成员中调用Equals来比较松散的结构。
这解释了SLaks的观点,即它的结构都是双重的; 这样的结构总是紧凑的。
不幸的是,正如我们在这里看到的那样,引入了语义差异,因为双打的比较比较和双打的等于比较给出不同的结果。
半个答案:
reflection器告诉我们, 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)]
)并且没有可用的源。
回到猜测为什么可以通过比特来比较一个案例,而另一个不可能(alignment问题可能?)
Mono的gmcs 2.4.2.3对我来说确实如此。
更简单的testing案例:
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个字节的倍数。
它必须与逐位比较相关,因为0.0
应该仅与信号位的-0.0
不同。
…你怎么看待这件事?
总是在值types上覆盖Equals和GetHashCode。 这将是快速和正确的。
如果你这样做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; }
这仍然是错误的。
如果结构只有双打, 我似乎是假的。
只是这个10年的bug的更新:它已被固定 ( 免责声明 :我是这个公关的作者)在.NET核心将可能在.NET核心2.1.0发布。
博客文章解释了这个错误以及我如何修复它。
它必须是零相关的,因为改变了行
dd = -0.0
至:
dd = 0.0
结果是比较是真实的…