美文网首页
第一章 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 对象判等

        因为对象复制(克隆)的一个前提条件就是:要能够知道复制前后的两个对象是否相 等,所以,在展开对象复制的内容...

  • C#语言入门详解006

    006 C#类型、变量与对象详解 目录 *什么是类型*类型在C#语言中的作用*C#语言的类型系统*变量、对象与内存...

  • 第一章 C# 类型基础--->1.3 对象复制

    有的时候,创建一个对象可能会非常耗时,比如对象需要从远程数据库中获取数据来填 充,又或者创建对象需要读取硬盘文件。...

  • 2017 10 16

    今天就C#语言基础进行学习,主要包括数据类型 (*)、变量与常量、String类型、类型转换 (**)等相关内容。...

  • C# 枚举器和可枚举类型

    在C#中能够使用foreach语句遍历数组和List等对象的原因就在于这些对象是可枚举类型,这些可枚举类型能够获取...

  • 2017 10 17

    今天一天就C#语言基础进行学习主要内容包括:字符类型、变量与常量、String类型等内容。 声明变量: 变量类型 ...

  • .NET Core C# 初级篇 1-1 基础类型介绍

    .NET Core CSharp初级篇 1-1 本节内容是对于C#基础类型的存储方式以及C#基础类型的理论介绍 基...

  • Winform

    C# WinForm实践开发教程 C# WinForm实践开发教程——第一章 Windows编程基础C# WinF...

  • 第一部分:C#语言基础

    第1章 C#类型基础 C#中的两种类型:值类型和引用类型 1.1 值类型和引用类型 值类型包括了结构和枚举,引用类...

  • 2017-07-03

    0、基础语法 Javascript基础语法包括:变量定义、数据类型、循环、选择、内置对象等。 数据类型有strin...

网友评论

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

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