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

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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值