原文:http://blogs.msdn.com/xiangfan/archive/2008/09/01/magic-behind-valuetype-equals.aspx
在"Effective C#"一书中,Bill Wagner写到:“在你创建值类型的时候,一定要重写ValueType.Equals()”。他主要考虑的是性能问题,在ValueType.Equals函数的默认实现里,需要使用反射枚举它所有的成员变量,开销是比较大的。
实际上,公共语言运行时针对“简单”值类型作了特殊的优化。让我们来瞧一瞧。
这是rotor中ValueType.Equals函数的实现("clr/src/bcl/system/valuetype.cs"):
bool Equals(object obj)
{
//Compare type
//if there are no GC references in this object we can avoid reflection
//and do a fast memcmp
if (CanCompareBits(this))
{
return FastEqualsCheck(this, obj);
}
//Compare using reflection
}
这里最重要的两个函数"CanCompareBits"和"FastEqualsCheck"。它们都被标记了"[MethodImpl(MethodImplOptions.InternalCall)]"属性,说明它们的实现是在公共语言运行时的内部的。
通过浏览rotor的源代码,你可以在"clr/src/vm/comutilnative.cpp"中找到这两个函数的实现。
CanCompareBits函数的注释写到:“如果值类型不包含指针或者没有填充,将返回true”。而FastEqualsCheck函数使用"memcmp"进行快速的二进制比较。
你会说,这个优化实现很不错啊,我的值类型很简单,不用自己重写Equals函数了。但是请等一下,你有没有发现CanCompareBits的问题?
问题是注释中提到的条件并不能保证二进制的比较能够得到正确的结果。
设想一下,你有一个值类型A,它只有一个float类型的成员f。你定义了两个A的对象a和b,a.f=+0.0,b.f=-0.0。从逻辑上来讲,它们应该是相等的,但是它们的二进制表示却是不同的。因此没有优化的代码将返回true,而优化之后的版本将返回false。
类似的,如果你的值类型中包含了其它重写了Equals函数的值类型,那么上述的优化都可能导致错误的结果。
这是公共语言运行时的一个bug,但是它也告诉我们,你最好为你的值类型重写Equals函数 :-)