Net中对象相等比较是看似简单实际上有点儿复杂这和现实世界的情况差不多无论人或物现实中没有两个绝对相等只有相对的属性一致或同属某个类别这学问细究下去无穷无尽一辈子也未必参得透而Net中的相等没有那么捉摸不透却也值得品味一番
说到相等新手上来先学到的就是相等操作符==(有的Net语言中是单=)这个很自然问题是有不少人工作了一两年提到相等还是只想到操作符就太片面了
在这里茴香豆的茴字有四种写法Net中也主要有四种相等比较分别是==操作符ObjectEquals方法ObjectReferenceEquals方法对象实例的Equals方法
先来看Object的两个静态方法它们逻辑比较简单ReferenceEquals方法是比较两个对象的引用是否相同即栈上的地址是否一样对于值类型没有意义参数中若有值类型参数出现必定返回false它主要用来测试实际应用开发中很少用到方法名也有点长对于引用类型如果方法结果为True这个相等是最严格最纯粹如假包换的相等说明这两个参数其实是同一个对象当然无论用其他哪种相等比较方式同样也应返回True
Object的Equals静态方法实际上是对实例Equals方法的扩展增加了null的判断适应于比较两个可能为空引用的对象对于值类型和Equals实例方法功能完全一样
再来看==我们天天打交道的这小小操作符并不那么简单上面我们说两个Object静态方法区别在值类型和引用类型上对于其他相等比较区别也主要在此一般情况下不是所有对于引用类型==和ReferenceEquals静态方法作用相同值类型在这里则有区分对于原生值类型如intdoublelongchar等==是直接比较其数值而且不同类型间可以互相比较比如int和charA==返回的是True而对于一般的Struct如果没有在代码中定义==(也包括!=)操作符是不能用==比较的
引用类型也可以定义==操作符覆盖CLR原生支持的比较最常见的是String类型它就定义了==操作符很合理地放宽了相等的条件使得String类型像原生值类型一样按值比较String类的==操作符其实就是直接调用的被自己重写过Equals方法
String类是最常用也最特别的一个类大部分面试都会问到String的特点除了不可变和内存驻留机制外其他主要特点就是相等的特殊性了
最后就来说说实例Equals方法吧这是个Virtual方法是我们在应用开发中经常要根据业务逻辑需要进行覆写的方法定义并使用操作符固然方便不过除了像String之类的特殊情况引用类型让==保持默认规则是更好的选择而让Equals方法实现业务上的值相等如果不覆写Equals方法也是比较对象的引用
对于值类型实现==操作像一个点缀而如果想实现相等比较操作应该优先重写Equals方法(同样若要实现大小比较应该优先实现IComparable接口而不是实现比较操作符)从Object继承的Equals方法用于值类型时比较两个对象的所有字段全相等才为True要注意它据说用了反射效率很低的但是它低归低为什么一定要优先重写它?
因为所有Net Framework键值集合都是用Equals实例方法做比较的所以它实际上成了Net中的法定天平无论是原生类型结构或类的实例都应以Equals方法作为其标准的相等比较方式包括我们自己实现的类型用实例方法的好处也可以理解更灵活我们可以添加一些重载的Equals方法申明不同的比较前提条件与重写的默认Equals方法配合构成一套完整的比较规则以符合现实里复杂多变的标准
重写Equals方法时官方推荐重写GetHashCode方法要是你不用此类型作键值集合键的话其实无所谓
个别情况中复杂到重载Equals方法也力不从心时我们就要定义专门用来比较相等的功能类Net Framework已经提供了一个接口SystemCollectionsIEqualityComparer并有几个内置的实现如StringComparerEqualityComparer我们自己写的比较类也不妨实现这个接口当然只要能用也不必计较那么多看Net Framework源代码能发现好多个乱七八糟的类用于比较相等大概是内部特权吧
结尾外总结一张表可以一目了然