随笔 - 13, 文章 - 0, 评论 - 3, 引用 - 0
数据加载中……

Equals方法的实现(参见《Microsoft.net框架程序设计》并提出少许建议)

从《Microsoft.net框架程序设计》一书中,看到Equals的实现基本分为如下三类(顺序有所调整):
(1)引用类型,从MyRefType到Object的继承链上(基类、基类的基类、...),有类覆盖了Object的Equals方法实现;
(2)引用类型,从MyRefType到Object的继承链上(基类、基类的基类、...),均没有类覆盖Object的Equals方法实现;
(3)值类型的Equals方法实现。
分法相当科学,不过我看了其中的代码实现,针对第二种实现有一些自己的疑惑和想法。

为了没有看过该书的同仁们更好地理解,我将书中的实现贴在此文中。
先看书中第一种,(1)引用类型,从MyRefType到Object的继承链上(基类、基类的基类、...),有类覆盖了Object的Equals方法的实现:

 1//引用类型,从MyRefType到Object的继承链上(基类、基类的基类、),有类覆盖了Object的Equals方法
 2    class MyRefType : BaseType
 3    {
 4        RefType refobj;     //该字段是一个引用类型
 5        ValType valobj;     //该字段是一个值类型
 6
 7        public override bool Equals(object obj)
 8        {
 9            //首先让基类比较其中的字段
10            if (!base.Equals(obj))
11                return false;
12
13            //因为this不是null,所以如果obj是null,那两个对象将不可能相等
14            if (obj == null)
15                return false;
16
17            //如果类型不同,不可能相等
18            if (this.GetType() != obj.GetType())
19                return false;
20            //将obj转型为定义的类型以访问其中的字段。
21            //注意这里的转型不会失败,因为已经知道两个对象是同一个类型
22            //obj的运行时类型要么是MyRefType,要么是MyRefType的子类
23            MyRefType other = (MyRefType)obj;
24
25            //比较其中的引用类型字段
26            //这里调用Object的static 方法Equals,是为了兼容考虑refobj或者other.refobj为null的情况
27            if (!Object.Equals(refobj, other.refobj))
28                return false;
29
30            //比较其中的值类型字段,
31            //因为值类型不可能为null,所以直接调用值类型的Equals方法,免得装箱的开销
32            if (!valobj.Equals(other.valobj))
33                return false;
34
35            return true;    //到这里两个对象才算相等
36        }

37    }

其中用到了Object的Equals方法,将原代码贴出来

 1    class Object
 2    {
 3        public virtual Boolean Equals(Object obj)
 4        {
 5            //如果两个引用指向同一个对象
 6            //它们肯定相等
 7            if (this == obj)
 8                return true;
 9            return false;
10        }

11
12        public static Boolean Equals(Object objA, Object objB)
13        {
14            //指向同一个对象,返回true
15            if (objA == objB)
16                return true;
17
18            //如果两者任何一个为null,则不可能相等,返回false
19            if ((objA == null||
20                (objB == null))
21                return false;
22            
23            //判断objA和objB是否相等,返回比较结果
24            return objA.Equals(objB);
25        }

26        
27    }

在这种实现中,个人认为Equals方法是没有问题的,首先调用

 //首先让基类比较其中的字段
            if (!base.Equals(obj))
                
return false;

比较了基类的部分,然后就比较自己的部分就OK了。
再看书中的第二种,2)引用类型,从MyRefType到Object的继承链上(基类、基类的基类、...),均没有类覆盖Object的Equals方法的实现;

 1//引用类型,从MyRefType到Object的继承链上,均没有类覆盖Object的Equals方法
 2    class MyRefType : BaseType
 3    {
 4        RefType refobj;     //该字段是一个引用类型
 5        ValType valobj;     //该字段是一个值类型
 6
 7        public override bool Equals(object obj)
 8        {
 9            //因为this不是null,所以如果obj是null,那两个对象将不可能相等
10            if (obj == null)
11                return false;
12
13            //如果类型不同,不可能相等
14            if (this.GetType() != obj.GetType())
15                return false;
16            //将obj转型为定义的类型以访问其中的字段。
17            //注意这里的转型不会失败,因为已经知道两个对象是同一个类型
18            //obj的运行时类型要么是MyRefType,要么是MyRefType的子类
19            MyRefType other = (MyRefType)obj;
20
21            //比较其中的引用类型字段
22            //这里调用Object的static 方法Equals,是为了兼容考虑refobj或者other.refobj为null的情况
23            if (!Object.Equals(refobj, other.refobj))
24                return false;
25
26            //比较其中的值类型字段,
27            //因为值类型不可能为null,所以直接调用值类型的Equals方法,免得装箱的开销
28            if (!valobj.Equals(other.valobj))
29                return false;
30
31            return true;    //到这里两个对象才算相等
32        }

33    }

我们可以发现,同第一种实现的差别在于没有了如下代码段:

 //首先让基类比较其中的字段
            if (!base.Equals(obj))
                
return false;

为什么不能加这一段呢?因为在这种实现环境下,我们设定了前提:从MyRefType到Object的继承链上,均没有类覆盖Object的Equals方法。所以如果加了上面这段代码,那实际调用的是Object类的实例方法Equals

1public virtual Boolean Equals(Object obj)
2        {
3            //如果两个引用指向同一个对象
4            //它们肯定相等
5            if (this == obj)
6                return true;
7            return false;
8        }

这样,如果this和obj不指向同一个对象,则这个base.Equals(obj)肯定返回false,于是Equals也返回false,而在this和obj指向同一个对象,Equals返回true,这样不就是Object的Equals()实现吗?既然如此,何苦自己重新这个函数呢?既然重写了,那肯定有不一样的行为,于是下面的代码段加入不得。

 //首先让基类比较其中的字段
            if (!base.Equals(obj))
                
return false;


到这里,我们也可以理解。不过问题是,没有加入这段代码,从“(2)引用类型,从MyRefType到Object的继承链上(基类、基类的基类、...),均没有类覆盖Object的Equals方法的实现”中,我们好像找不到比较基类成员的影子。不比较基类成员,是否合理呢?

看下面例子,如果两个Derived对象,无需比较Base部分的成员变量,那么下面的写法是正确的,此时该程序打印出的结果为True。

 1using System;
 2using System.Collections.Generic;
 3using System.Text;
 4
 5namespace MyTest
 6{
 7    //定义一个引用类型
 8    class MyRefType
 9    {
10        public MyRefType(int value)
11        {
12            this.value = value;
13        }

14
15        private int value;
16    }

17    //定义一个值类型
18    struct MyValueType
19    {
20        public MyValueType(int value)
21        {
22            this.value = value;
23        }

24
25        private int value;
26    }

27
28    //基类
29    class Base
30    {
31        //基类的引用类型成员,为了代码简单,这里声明为public
32        public MyRefType baseRef;    
33        //基类的值类型成员,为了代码简单,这里声明为public
34        public MyValueType baseValue;   
35    }

36    
37    //子类
38    class Derived:Base
39    {
40        //子类的引用类型成员,为了代码简单,这里声明为public
41        public MyRefType derivedRef; 
42        //子类的引用类型成员,为了代码简单,这里声明为public
43        public MyValueType derivedValue;
44
45        //这里Base没有覆盖Object的Equals()方法,所以这里采用Equals()的第二种实现方式
46        //(2)引用类型,从MyRefType到Object的继承链上,均没有类覆盖Object的Equals方法
47        public override Boolean Equals(Object obj)
48        {
49            if (obj == null)
50                return false;
51            if (this.GetType() != obj.GetType())
52                return false;
53            Derived other = (Derived)obj;
54
55            if (!Object.Equals(this.derivedRef, other.derivedRef))
56                return false;
57
58            if (!derivedValue.Equals(other.derivedValue))
59                return false;
60          
61            return true;
62        }
        
63    }

64         
65
66    class Program
67    {
68        static void Main(string[] args)
69        {
70            MyRefType refType1 = new MyRefType(1);
71            MyRefType refType2 = new MyRefType(2);
72            MyRefType refType3 = new MyRefType(3);
73
74            Derived d1 = new Derived();
75            Derived d2 = new Derived();
76
77            d1.derivedRef = refType1;
78            d1.baseRef = refType2;
79
80            d2.derivedRef = refType1;
81            d2.baseRef = refType3;
82
83            System.Console.Write(d1.Equals(d2));
84            System.Console.Read();            
85        }

86    }
    
87}

88

但是有时逻辑要求,两个Derived对象需要比较Base部分的成员变量,那上面的写法就是错误的,此时应该修改为如下的代码,该程序打印出的结果为False。

 1using System;
 2using System.Collections.Generic;
 3using System.Text;
 4
 5namespace MyTest
 6{
 7    //定义一个引用类型
 8    class MyRefType
 9    {
10        public MyRefType(int value)
11        {
12            this.value = value;
13        }

14
15        private int value;
16    }

17    //定义一个值类型
18    struct MyValueType
19    {
20        public MyValueType(int value)
21        {
22            this.value = value;
23        }

24
25        private int value;
26    }

27
28    //基类
29    class Base
30    {
31        //基类的引用类型成员,为了代码简单,这里声明为public
32        public MyRefType baseRef;    
33        //基类的值类型成员,为了代码简单,这里声明为public
34        public MyValueType baseValue;   
35    }

36    
37    //子类
38    class Derived:Base
39    {
40        //子类的引用类型成员,为了代码简单,这里声明为public
41        public MyRefType derivedRef; 
42        //子类的引用类型成员,为了代码简单,这里声明为public
43        public MyValueType derivedValue;
44
45        //这里Base没有覆盖Object的Equals()方法,所以这里采用Equals()的第二种实现方式
46        //(2)引用类型,从MyRefType到Object的继承链上,均没有类覆盖Object的Equals方法
47        public override Boolean Equals(Object obj)
48        {
49            if (obj == null)
50                return false;
51            if (this.GetType() != obj.GetType())
52                return false;
53            Derived other = (Derived)obj;
54
55            if (!Object.Equals(this.derivedRef, other.derivedRef))
56                return false;
57
58            if (!derivedValue.Equals(other.derivedValue))
59                return false;
60
61            ///////////////////////////////////////////////////////////
62            //这里要加入比较基类的成员部分,具体比较什么视实际需要而定
63            if (!Object.Equals(this.baseRef, other.baseRef))
64                return false;
65
66            if (!baseValue.Equals(other.baseValue))
67                return false;
68            //////////////////////////////////////////////////////////
69
70            return true;
71        }
        
72    }

73         
74
75    class Program
76    {
77        static void Main(string[] args)
78        {
79            MyRefType refType1 = new MyRefType(1);
80            MyRefType refType2 = new MyRefType(2);
81            MyRefType refType3 = new MyRefType(3);
82
83            Derived d1 = new Derived();
84            Derived d2 = new Derived();
85
86            d1.derivedRef = refType1;
87            d1.baseRef = refType2;
88
89            d2.derivedRef = refType1;
90            d2.baseRef = refType3;
91
92            System.Console.Write(d1.Equals(d2));
93            System.Console.Read();            
94        }

95    }
    
96}

97


再看第三种实现:(3)值类型的Equals方法实现。书中代码如下:

 1//值类型
 2    struct MyValType
 3    {
 4        RefType refobj;     //引用类型
 5        ValType valobj;     //值类型
 6
 7        public override bool Equals(object obj)
 8        {
 9            //这里不用GetType(),可以避免装箱
10            //同时,因为值类型不能有子类,所以这里用is就可以达到类型比较的目的
11            if (!(obj is MyValType))
12                return false;
13            return this.Equals((MyValType)obj);
14        }

15
16        public Boolean Equals(MyValType obj)
17        {
18            if (!Object.Equals(this.refobj, obj.refobj))
19                return false;
20            if (!this.valobj.Equals(obj.valobj))
21                return false;
22            return true;
23        }

24    }

这种实现方法,如果不存在其它类对MyValType的隐式类型转换,是没有问题的,如果存在隐式类型转换,那代码中的强类型Equals()方法(从第16行开始)就可能存在问题了。试想想,两种不同类型的实例,我们有多少场合会认为他们Equals呢?根据逻辑来定,一般不会认为它们相同。
为了说明存在的这个问题,请看如下代码:

 1 //值类型
 2    struct MyValType
 3    {
 4        private int value;     //值类型
 5
 6        public int Value
 7        {
 8            get return this.value; }
 9            set this.value = value; }
10        }

11
12        public override bool Equals(object obj)
13        {
14            //这里不用GetType(),可以避免装箱
15            //同时,因为值类型不能有子类,所以这里用is就可以达到类型比较的目的
16            if (!(obj is MyValType))
17                return false;
18            return this.Equals((MyValType)obj);
19        }

20
21        public bool Equals(MyValType obj)
22        {
23            if (value != obj.Value)
24                return false;
25            return true;
26        }

27    }

28
29    //值类型
30    struct MyValType2
31    {
32        private int value;     //值类型
33
34        public int Value
35        {
36            get return this.value; }
37            set this.value = value; }
38        }

39
40        public static implicit operator MyValType(MyValType2 obj)
41        {
42            MyValType myValType = new MyValType();
43            myValType.Value = obj.Value;
44            return myValType;
45        }

46    }

47
48    class Program
49    {
50        static void Main(string[] args)
51        {
52            MyValType myValue1 = new MyValType();
53            myValue1.Value = 10;
54
55            MyValType2 myValue2 = new MyValType2();
56            myValue2.Value = 10;
57
58            //这里会输出True,从常理上说不应该,从程序逻辑上就是True
59            //程序先将myValue2隐式转换为MyValType
60            //然后调用函数public bool Equals(MyValType obj);
61            System.Console.WriteLine(myValue1.Equals(myValue2));
62            System.Console.Read();
63        }

64    }

上面代码中,一般从常理来说,我们不会认为myValue1和myValue2相等,因为他们是不同类型的实例(myValue1属于MyValType类型,myValue2属于MyValType2类型)。但是实际输出了结果True。个中原因在于定义了一个从MyValType2到MyValType的隐式转换:

1public static implicit operator MyValType(MyValType2 obj)
2        {
3            MyValType myValType = new MyValType();
4            myValType.Value = obj.Value;
5            return myValType;
6        }

于是在运行语句myValue1.Equals(myValue2)时,会先将myValue2转换为一个MyValType类型的临时变量,然后用MyValue1和这个临时变量比较,此时调用强类型的比较函数,因为MyValType和MyValType2这两个类型内部结构一样,且两变量的内部field的值相同,所以返回True。
上面例子说明 ,如果不能确保没有其他类型到该类型的隐式转换,那万无一失的办法就是不实现强类型的Equals。调用默认的Equals方法或者实现参数为Object的Equals方法,虽然效率可能差一点,但是可靠。当然,如果可以确认没有其他类型到该类型的隐式转换,那实现强类型的Equals方法还是可以带来效率的提升。(相同描述可参见《.net框架程序设计》)。

关于Equals()方法的实现,基本上也就到此结束了。如果各位有什么好的心得,欢迎积极探讨。

posted on 2009-04-26 23:33 五味杂陈 阅读(1447) 评论(0)  编辑 收藏 引用 所属分类: .NET


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理