Magic behind ValueType.Equals

公共语言运行时对简单值类型的Equals方法进行了特殊优化,通过快速二进制比较提高性能。但此优化存在缺陷,如对于含浮点数的值类型可能因正负零不同导致误判。建议开发者自行重写Equals确保准确性。

原文: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函数 :-)

这两个条件表达式看起来相似,但实际上它们的逻辑含义是完全不同的。让我们逐一分析: ### 表达式 1: `(!value.equals("02") && !value.equals("14"))` 这个表达式的含义是:**`value`既不是"02"也不是"14"的情况才会返回true**。 #### 分析步骤: - `!value.equals("02")`: 判断`value`是否不等于字符串“02”。 - `!value.equals("14")`: 判断`value`是否不等于字符串“14”。 - 这两个判断通过逻辑运算符`&&`(与)连接起来。也就是说只有当`value`同时满足以下两点时整个表达式才为真: - 它不等于"02" - 并且它也不等于"14" 例如: ```java if (!value.equals("02") && !value.equals("14")) { System.out.println("Value is neither '02' nor '14'"); } ``` 如果`value = "03"` 或者其他值如 `"abc"`,此条件将成立;但如果`value="02"`或`value="14"`,则该条件会失败。 --- ### 表达式 2: `!(value.equals("02") && value.equals("14"))` 这个表达式的含义是:否定(`!`)了下面的结果——即 **`value`不可能同时既是"02"又是"14"**, 因为一个变量在同一时刻只能有一个具体的值(除非存在复杂的自定义equals方法覆盖),所以这种直接对比基本等同于永远为True. #### 分析步骤: - `(value.equals("02") && value.equals("14"))`: 检查`value`是否等于"02",并且也检查其是否还等于"14"。 然而,在正常情况下没有任何单个对象能同时匹配上述两者的状态(除非特殊场景)。因此这里的整体内容几乎总是false, 再取反后结果就恒定地变为 true 。 总结来说, 第一种情况我们真正关心的是排除明确指定的一些特定值; 而第二种实际上是一个冗余设计因为最终都会得到相同的结论不管输入为何只要符合标准java string比较规则下运行环境。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值