第一章 C# 类型基础--->1.2 对象判等

    因为对象复制(克隆)的一个前提条件就是:要能够知道复制前后的两个对象是否相 等,所以,在展开对象复制的内容前,有必要先了解如何进行对象判等。

1.2.1 引用类型判等

    首先看一下引用类型对象的判等,大家知道在System.Object基类型中,定义了实例方 法Equals(Object obj),静态方法Equals(Object objA,Object objB),静态方法 ReferenceEquals(Object objA,Object objB) 这三个方法来进行对象的判等。
先看看这三个方法是如何实现的,注意在代码中用“#+数字”标识的地方,后文会直接 引用:

public static bool ReferenceEquals (Object objA, Object objB) 
{     return objA == objB;                       // #1 
} 
public virtual bool Equals(Object obj) 
{    return InternalEquals(this, obj);   // #2
 }
 public static bool Equals(Object objA, Object objB) {     
if (objA==objB) {                        // #3         
return true;     
}      
if (objA==null || objB==null) {         return false;      }      return objA.Equals(objB);          // #4 
}
实例一:
// 复制对象引用
 bool result; 
RefPoint rPoint1 = new RefPoint(1); 
RefPoint rPoint2 = rPoint1; 
result = (rPoint1 == rPoint2);                // 返回 true; 
Console.WriteLine(result); 
result = rPoint1.Equals(rPoint2);        // #2 返回true; 
Console.WriteLine(result);

    在阅读本节时,应该时刻在脑子里构思一个栈和一个堆,并思考着每条语句会在这两种 结构上产生怎么样的效果。在这段代码中,产生的效果如图1-5所示:在堆上创建了一个新 的RefPoint类型的对象实例,并将它的x字段初始化为1;在栈上创建RefPoint类型的变量 rPoint1,rPoint1保存了堆上这个对象的地址;而将rPoint1赋值给rPoint2时,此时并没有在 堆上创建一个新的对象,而是将之前创建的对象的地址复制到了rPoint2。此时,rPoint1和 rPoint2指向了堆上同一个对象。


2789632-45d73b9cc5254f32.png

从ReferenceEquals()这个方法名就可以看出,它判断两个引用变量是不是指向了同一个 变量,如果是,那么就返回true。这种相等叫做引用相等(rPoint1==rPoint2等效于 ReferenceEquals)。因为它们指向的是同一个对象,所以对rPoint1的操作将会影响 rPoint2。

实例二:
//创建新引用类型的对象,其成员的值相等 
RefPoint rPoint1 = new RefPoint(1); 
RefPoint rPoint2 = new RefPoint(1); 
result = (rPoint1 == rPoint2); 
Console.WriteLine(result);                // 返回 false; 
result = rPoint1.Equals(rPoint2); 
Console.WriteLine(result);                // #2 返回false

上面的代码在堆上创建了两个类型实例,并用同样的值初始化它们;然后将它们的地址 分别赋给栈上的变量rPoint1和rPoint2。此时#2返回了false,可以看到,对于引用类型,即 使类型的实例(对象)包含的值相等,如果变量指向的是不同的对象,那么也不相等。

1.2.2 简单值类型判等

注意本节的标题:简单值类型判等,这个简单是如何定义的呢?如果值类型的成员仅包 含值类型,那么暂且管它叫简单值类型;如果值类型的成员包含引用类型,则管它叫复杂值 类型。
应该还记得之前提过,值类型都会隐式地继承自System.ValueType类型,而ValueType 类型覆盖了基类System.Object类型的Equals()方法,在值类型上调用Equals()方法,会调用 ValueType的Equals()。所以,先看看这个方法是什么样的,依然用#number标识后面会引 用的地方。

public override bool Equals (Object obj) {   
if (null==obj) {        
return false;   }    
RuntimeType thisType = (RuntimeType)this.GetType();   
RuntimeType thatType = (RuntimeType)obj.GetType();  
 if (thatType!=thisType) {         // 如果两个对象不是一个类型,直接返回false       return false;          
 }    
Object thisObj = (Object)this;   
Object thisResult, thatResult;   
 if (CanCompareBits(this))                                        // #5       return FastEqualsCheck(thisObj, obj);        // #6        // 利用反射获取值类型所有字段   FieldInfo[] thisFields = thisType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);         // 遍历字段,进行字段对字段比较   
for (int i=0; i<thisFields.Length; i++) {       
 thisResult = ((RtFieldInfo)thisFields[i]).InternalGetValue(thisObj,false);       thatResult = ((RtFieldInfo)thisFields[i]).InternalGetValue(obj, false);       
if (thisResult == null) {            
if (thatResult != null)               return false;       
 }        else       if (!thisResult.Equals(thatResult)) {  // #7           
return false;       
} 
  }   
return true; 
}
实例一:
ValPoint vPoint1 = new ValPoint(1);
 ValPoint vPoint2 = vPoint1; 
result = (vPoint1 == vPoint2);        //编译错误:不能在ValPoint上应用 "==" 操作符 
Console.WriteLine(result);        
result = Object.ReferenceEquals(vPoint1, vPoint2);        // 隐式装箱,指向了堆上的不同对象 
Console.WriteLine(result); 

上面的代码先在栈上创建了一个变量vPoint1,由于ValPoint是结构类型,因此变量本身 已经包含了所有字段和数据。然后在栈上复制了vPoint1的一份副本给了vPoint2。如果依照 前面的惯性思维去考虑,那么就会认为它们应该是相等的。然而,接下来试着去比较它们, 就会看到,不能用“==”直接去判断,这样会返回一个编译错误“不能在ValPoint上应用==操 作符”。
如果调用System.Object基类的静态方法ReferenceEquals(),就会发生有意思的事情: 它返回了false。为什么呢?看下ReferenceEquals()方法的签名就可以了,它接受的是Object 类型,也就是引用类型,而当传递vPoint1和vPoint2这两个值类型的时候,会进行一个隐式 的装箱,效果相当于下面的语句:

Object boxPoint1 = vPoint1; 
Object boxPoint2 = vPoint2; 
result = (boxPoint1 == boxPoint2);                 // 返回false 
Console.WriteLine(result);

装箱的过程,在前面已经讲述过,上面的操作等于在堆上创建了两个对象,对象包含的 内容相同,但对象所在的地址不同。最后将对象地址分别返回给堆栈上的boxPoint1和 boxPoint2变量,再去比较boxPoint1和boxPoint2是否指向同一个对象,显然不是了,所以 返回了false。

result = vPoint1.Equals(vPoint2);                // #5 返回true; #6 返回true; 
Console.WriteLine(result);                       // 输出true

因为它们均继承自ValueType类型,所以此时会调用ValueType上的Equals()方法,在方 法体内部,#5处的CanCompareBits(this) 返回了true。CanCompareBits(this)这个方法,按 微软的注释,意思是说:如果对象的成员中存在对于堆上的引用,那么返回false,如果不存 在,返回true。按照ValPoint的定义,它仅包含一个int类型的字段x,自然不存在对堆上其他
对象的引用,所以返回了true。从#5处的名字CanCompareBits可以看出,是在判断是否可 以进行按位比较,因此返回了true以后,#6自然是进行按位比较了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值