当前位置:K88软件开发文章中心编程语言.NET.NET01 → 文章内容

重载Object的虚方法——重载Equals和运算符

减小字体 增大字体 作者:佚名  来源:翔宇亭IT乐园  发布时间:2018-12-31 11:22:19

:2010-01-28 09:12:00

Object是所有类的顶级父类,而Object又提供了四个虚方法:

Equals , GetHashCode, ToString, Finalize。

那么在这个系列文章中,我们就看下,我们对这四个方法的利用。

首先是引用类型重载Equals,我分成三步:

1. 空值验证

2. 类型验证

3. 比较验证

代码如下:


class Person{
public string Name {
get;
set; }
public int Age {
get;
set; }
public City MyCity {
get;
set; }
public override bool Equals(
object obj) {
if (obj ==
null) {
return false; }
if (obj.GetType() !=
this.GetType()) {
return false; } Person personTemp = obj
as Person;
if (!Object.Equals(
this.MyCity, personTemp.MyCity)) {
return false; }
if (
this.Age != personTemp.Age ||
this.Name != personTemp.Name) {
return false; }
return true; }}

 在此需要注意的是,在比较引用类型属性的值是,需要使用Object的静态方法去比较,主要是为了防止属性值为null而抛出异常。我们来看下Object的静态Equals实现就明白了:


public static bool Equals(
object objA,
object objB){
return ((objA == objB) || (((objA !=
null) && (objB !=
null)) && objA.Equals(objB)));}

 呵呵,很漂亮的实现。解释一下,其实就是首先比较两者是否指向同一块引用,然后判断两者是否都不为空,最后来调用类型的Equals重载方法。

接下来,我们看下,如果这个时候我们实现了一个Person类的子类,我们该怎么写?


class Programmer:Person{
public int CodeRowCount {
get;
set; }
public override bool Equals(
object obj) {
if (!
base.Equals(obj)) {
return false; } Programmer pTemp = (Programmer)obj;
if (pTemp.CodeRowCount !=
this.CodeRowCount) {
return false; }
return true; }}

 来简单解释一下,由于Person已经判断了obj是否为空啊,类型是否相等,基类的字段是否相等,因此我们不需要再操心了,我们只需要比较子类独有的字段是否相等即可。

这里我们强调下,在Object默认的Equals实现中,比较的是两个对象是否指向了同一个引用,因此,如果我们的父类没有重载Equals方法,那么我们的这个版本将永远都是错误的,因此,我们也可以看出实现Equals方法的重要性吧,呵呵!

最后是值类型(主要是结构体)的重载Equals的方法,首先让我们看看所有值类型的父类System.ValueType对于Equals的实现:


public override bool Equals(
object obj){
if (obj ==
null) {
return false; } RuntimeType type = (RuntimeType)
base.GetType(); RuntimeType type2 = (RuntimeType)obj.GetType();
if (type2 != type) {
return false; }
object a =
this;
if (CanCompareBits(
this)) {
return FastEqualsCheck(a, obj); } FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
for (
int i = 0; i < fields.Length; i++) {
object obj3 = ((RtFieldInfo)fields[i]).InternalGetValue(a,
false);
object obj4 = ((RtFieldInfo)fields[i]).InternalGetValue(obj,
false);
if (obj3 ==
null) {
if (obj4 !=
null) {
return false; } }
else if (!obj3.Equals(obj4)) {
return false; } }
return true;}

 方法很长,我来解释一下:

首先,依然是来判断obj是否为空;

接下来,来得到两个对象的类型,在这里出现了一个类是RuntimeType,我们Reflector下这个类:

是一个Internal类型,程序集外无法访问,但是我们通过名称和其中的属性和方法名大概可以猜出,这是一个用于针对运行时反射而专门设计的类型。

接下来出现了CanCompareBitsFastEqualsCheck这两个方法,在Reflector中无法看到实现,但是根据方法名,我猜想应该是判断这个对象是否可以按位比较(我不是很理解,是指的序列化么?),如果可以的话,直接按位比较,这样的效率会比较高。(个人猜测,希望大家指点)

最后就是通过反射得到该对象中所有的属性,然后一一比较,不再赘述。

由此我们可以得知,System.ValueType以及为我们提供了很完善的实线,我们几乎不需要为之操心了,不过我们应该想到,在基类的实现中,这样的反射必定会浪费性能。那么我们的办法是为我们的结构体专门定制一个强类型的Equals方法:


struct ITWorker{
public string name;
public int age;
public City city;
public override bool Equals(
object obj) {
if (! (obj
is ITWorker)) {
return false; }
return this.Equals((ITWorker)obj); }
private bool Equals(ITWorker worker) {
if (!Object.Equals(
this.city, worker.city)) {
return false; }
if (!
this.name.Equals(worker.name) || ! (
this.age != worker.age)) {
return false; }
return true; }}

 合理重载了Equals方法后,我们的事情还不算结束,我们知道,C#提供了重载运算符的功能,而==和!=也经常被人所使用,而且经常用于和Equals相同的场合。那么我们就有必要再重载Equals的同时,重载运算符。


public static bool operator ==(Person p1, Person p2){
return p1.Equals(p2);}
public static bool operator !=(Person p1, Person p2){
return !(p1.Equals(p2));}

 就是这么简单。OK。原以为大功告成了,可是看看我的代码却发现了我的Person下出现了让我头疼的波浪线。提示的意思是说,我重载了Equals方法,但是却没有重载GetHashCode方法。


重载Object的虚方法——重载Equals和运算符