美文网首页
第一章 C# 类型基础--->1.2 对象判等

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

作者: 张中华 | 来源:发表于2018-12-17 23:44 被阅读15次

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

    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指向了堆上同一个对象。



    从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自然是进行按位比较了。

    相关文章

      网友评论

          本文标题:第一章 C# 类型基础--->1.2 对象判等

          本文链接:https://www.haomeiwen.com/subject/iivhkqtx.html