C#类型基础----对象判等

来源:互联网 发布:数据恢复软件的应用 编辑:程序博客网 时间:2024/06/06 05:41

C#类型基础----对象判等

 

前言

前面说了一点关于值类型和引用类型的东西,如果你能稍微有点收获,那将会是楼主的万幸!今天说一点关于对象判等的东西.因为对象复制(克隆)的一个前提条件就是:要能够知道复制前后的两个对象是否相等,所以,再战看对象赋值的内容前,有必要先了解如果进行对象判等.

 

正文

先定义用作示例的两个类型,他们代表一维坐标系(直线)上的地点,唯一区别是一个是引用类型class,一个是值类型struct:

    public class RefPoint    {        public int x;        public RefPoint(int x)         {             this.x = x;         }     }    public struct ValPoint    {        public int x;        public ValPoint(int x)        {            this.x = x;        }}


首先来看引用类型对象的判等,大家知道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;        }        public virtual bool Equals(object obj)        {            return InternalEquals(this,obj);        }        public static bool Equals(object objA, object objB)        {            if (objA==objB)            {                return true;            }            if (objA==null||objB==||)            {                return false;            }            return objA.Equals(objB);        } 

先看ReferenceEquals(object objA,object objB)方法,它实际上简单的返回objA==objB.再观察一下object.Equals()静态方法,如果任何一个对象引用为null,则总会返回false.当对象不为null,最后调用了实例上的Equals()方法.

 

            bool result;            RefPoint rPoint1 = new RefPoint(1);            RefPoint rPoint2 = rPoint1;            result = (rPoint1 == rPoint2);            Console.WriteLine(result);            result = rPoint2.Equals(rPoint1);            Console.WriteLine(result);            Console.Read(); 

在阅读本节的时候,应该时刻在脑子里构思一个栈和一个堆,并思考着每条语句会在这两种结构上产生怎么样的效果.在这段代码中,产生的效果如下图所示:在堆上创建了一个

新的RefPoint类型的对象实例,并将它的x字段初始化为1;在栈上创建RefPoint类型的变量rPoint1,rPoint1保存了堆上这个对象的地址;而将rPoint1赋值给rPoint2,此时并没有在堆上创建一个新的对象,而是将之前创建的对象的地址复制到了rPoint2.此时,rPoint1rPoint2指向了堆上同一个对象.

 

 

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

 

再看引用类型的第二种情况:

            //创建新引用类型的对象,其成员的值相等.            bool result;            RefPoint rPoint1 = new RefPoint(1);            RefPoint rPoint2 = new RefPoint(1);            result=(rPoint1==rPoint2);            Console.WriteLine(result);            result = rPoint1.Equals(rPoint2);            Console.WriteLine(result); 

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

 

简单值类型判等

 

咱们先研究一下简单值类型,这个简单的定义是怎样的呢?如果值类型的成员仅包含值类型,那么暂且管它叫简单值类型;如果值类型的成员包含引用类型,则管它叫复杂值类型.


前面说过,值类型都会隐式地继承System.ValueType类型,ValueType类型覆盖了基类System.Object类型的Equals()方法,在值类型上调用Equals()方法,会调用ValueTypeEquals().

 

先看第一段代码:

 

            //复制结构变量            bool result;            ValPoint vPoint1 = new ValPoint(1);            ValPoint vPoint2 = vPoint1;            result=(vPoint1==vPoint2);//这里出现编译错误:不能在ValPoint上应用"=="操作符            Console.WriteLine(result);            result = object.ReferenceEquals(vPoint1,vPoint2);//隐式装箱,指向了堆上的不同对象            Console.WriteLine(result);//返回false 

上面的代码先在栈上创建了一个变量vPoint1,由于ValPoint是结构类型,因此变量本身已经包含了所有字段和数据.然后在栈上复制了vPoint1的一份副本给了vPoint2.如果按照前面的思维去理解,那么肯定会认为它们应该是相等的.然而,接下来试着去比较它们,就会看到,不能用”==”直接去判断,这样会返回一个编译错误.

 

如果调用System.Object基类的静态方法ReferenceEquals(),就会发生有意思的事情:它返回了false.为啥?看下ReferenceEquals()方法的签名就可以了,他接受的是Object类型,也就是引用类型,而当传递vPoint1vPoint2这两个值类型的时候,会进行一个隐式的装箱,想过相当于下面的语句:

object boxPoint1=vPoint1;object boxPoint2=vPoint2;result=(boxPoint1==boxPoint2);//返回false

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

 

复杂值类型判等

 

重新定义一个新的结构ValLine,它代表直线上的线段,让它的一个成员为值类型ValPoint,一个成员为引用类型RefPoint,然后作比较.

using System;using System.Collections.Generic;using System.Data.SqlClient;using System.Linq;using System.Text;using System.Threading.Tasks; namespace ConsoleApplication1{    public class RefPoint    {        public int x;        public RefPoint(int x)        {            this.x = x;        }     }     public struct ValPoint    {        public int x;        public ValPoint(int x)        {            this.x = x;        }    }    public struct ValLine    {        public RefPoint rPoint;//引用类型成员        public ValPoint vPoint;//值类型成员        public ValLine(RefPoint rPoint, ValPoint vPoint)        {            this.rPoint = rPoint;            this.vPoint = vPoint;        }    }    class Program    {        static void Main(string[] args)        {                        bool result;            ValPoint vPoint = new ValPoint(1);            RefPoint rPoint = new RefPoint(1);            ValLine line1 = new ValLine(rPoint,vPoint);            ValLine line2 = line1;            result = line1.Equals(line2);            Console.WriteLine(result);            Console.Read();        }     } } 

这个案例的过程要复杂很多.在开始之前,先思考一下,当写下line1.Equals(line2),已经进行了一个装箱的操作.如果要进一步判等,显然不能去判断变量是否引用了堆上同一个对象,这样就没有意义了,对吧,因为总是会返回false(装箱后堆上创建了两个对象).那么应该如何判断呢?对堆上对象的成员(字段)进行一对一的比较,而成员又分为两种类型,一种是值类型,一种是引用类型.对于引用类型,去判断是否引用相等;对于值类型,如果是简单值类型,那么同前面说的一样去判断;如果是复杂类型,那么当然是递归调用了;最终确定要么是引用类型要么是简单值类型.

 

 

0 0
原创粉丝点击