C#温故知新(5)---关于相等

1.引言

相等,熟悉的陌生人吧,至少对于我来说是这样的。常用的相等有”==”,Equals(),ReferenceEquals()等等,他们之间有哪些区别?在哪些情况下使用哪个?本着这几个问题,我研究一下这一系列的”相等”,还是挖掘了不少好东西。本文不在传道受业,意在方便阅读复习,若篇中有纰漏乃之处,请留言指出,不必留情!

2.值类型和引用类型

老生常谈的一对概念,下面主要是介绍一下他们和”相等”有关的知识,如果想深入了解,可以自行去网上找一些资料,这方面的信息还是有很多的。

    /// <summary>
    /// Function:程序员类("相等测试")
    /// Programmer:WUC
    /// Date:2018/02/05
    /// </summary>
    public class Programmer
    {
        private string _name;
        private int _age;
        private string _idCardNum;
        private List<string> _language; 

        /// <summary>
        /// 姓名
        /// </summary>
        public string Name
        {
            get { return this._name; }
            set { this._name = value; }
        }

        /// <summary>
        /// 年龄
        /// </summary>
        public int Age
        {
            get { return this._age; }
            set { this._age = value; }
        }

        /// <summary>
        /// 省份证号码
        /// </summary>
        public string IdCardNum
        {
            get { return this._idCardNum; }
            set { this._idCardNum = value; }
        }

        /// <summary>
        /// 编程语言
        /// </summary>
        public List<string> Language
        {
            get { return this._language; }
            set { this._language = value; }
        }

        public Programmer(string _name,int _age,string _idCardNum,List<string> _language)
        {
            this._name = _name;
            this._age = _age;
            this._idCardNum = _idCardNum;
            this._language = _language;
        }

        public string Coding()
        {
            return string.Format("coding with {0} ...", string.Join("/", this._language));
        }

        public override string ToString()
        {
            return string.Format("name:{0};age:{1};idCardNum:{2};language:{3}", this._name, this._age, this._idCardNum, string.Join("/", this._language));
        }
    }
            int int_a = 2;
            int int_b = 2;
            int int_c = int_b;

            Programmer p_a = new Programmer("WUC", 25, "421127199205175214", new List<string>() { "C#", "Python" });
            Programmer p_b = new Programmer("WPY", 27, "421127199205175213", new List<string>() { "Java", "C++" });
            Programmer p_c = p_b;

经常说的值类型存储在栈(stack)上,引用类型存储在托管堆(heap)上是什么意思?不妨先看看上面的6个变量是怎么存储的吧。

这里写图片描述

说明:不由的要声明一下,看懂上面的图至关重要,赋值操作:类型 T a=b;不论类型T是什么类型,值类型也好,引用类型也罢,赋值操作都是在栈空间中开辟一个内存(Intptr的大小),然后把b的值copy一份给a,及是存储在a的栈空间中,只不过这个值对于值类型来说,就是我们常说的值类型的值(3,2,3.56),对于引用类型来说,就是变量的引用(类似008x什么的)。后续的问题就是要说明:这些相等比较到底特么是比较什么?是比较栈空间的值(可能是地址)?如果是地址,还是比较这个地址所对应的堆空间的值?

        static void Main(string[] args)
        {
            int int_a = 2;
            int int_b = 2;
            int int_c = int_b;

            Programmer p_a = new Programmer("WUC", 25, "421127199205175214", new List<string>() { "C#", "Python" });
            Programmer p_b = new Programmer("WPY", 27, "421127199205175213", new List<string>() { "Java", "C++" });
            Programmer p_c = p_b;

            Console.WriteLine("******************* == *******************");
            Console.WriteLine("int_a == int_b  {0}", int_a == int_b);
            Console.WriteLine("int_b == int_c  {0}", int_b == int_c);
            Console.WriteLine("p_a == p_b  {0}", p_a == p_b);
            Console.WriteLine("p_b == p_c  {0}", p_b == p_c);

            Console.WriteLine("******************* <T>.Equals(Object parameter) *******************");
            Console.WriteLine("int_a.Equals(int_b)  {0}", int_a.Equals(int_b));
            Console.WriteLine("int_b.Equals(int_c)  {0}", int_b.Equals(int_c));
            Console.WriteLine("p_a.Equals(p_b)  {0}", p_a.Equals(p_b));
            Console.WriteLine("p_b.Equals(p_c)  {0}", p_b.Equals(p_c));

            Console.ReadKey();

        }

这里写图片描述
这里写图片描述
提示:监视里面的 &变量名 的操作取得的地址只是栈空间的地址,因此 &p_c 和 &p_b的值是不一样的,*(&p_b)和 *(&p_c)才是堆空间的地址,结果显然是一样的。(相关指针的知识可以查阅一下资料哦)

3.==

操作符”==”比较的是什么(没有重写的前提下)?比较的是:栈空间里变量的值,如上面所说,这个值可能是个整型,也可能是个浮点型,还有可能是个堆空间的地址,甭管是个什么,==就是比较这个值,不是其他的,不是栈空间变量的地址,也不是堆空间的值,看看下面这张图吧(看清楚哦):
这里写图片描述

        static void Main(string[] args)
        {
            int int_a = 3;
            int int_b = 3;
            Console.WriteLine("整型:{0}", int_a == int_b);

            string str_a = "WUC";
            string str_b = "WUC";
            Console.WriteLine("字符串类型:{0}", str_a == str_b);

            Programmer p_a = new Programmer("WUC", 27, "421127199205175214", new List<string>() { "C#", "Python" });
            Programmer p_b = new Programmer("WPY", 27, "421127199205175213", new List<string>() { "Java", "C++" });
            Programmer p_c = p_b;

            Console.WriteLine("自定义类1:{0}", p_a == p_b);
            Console.WriteLine("自定义类2:{0}", p_c == p_b);

            Console.ReadKey();
        }

这里写图片描述
说明:值类型int_a和 int_b存的值都是3,因此结果为true,引用类型p_a,p_b,p_c其中p_b和p_c是的栈空间值是一样的,都是指向堆空间的某一个地址,因此他们的比较是p_a==p_b(false),p_c==p_b(true),这个因该都不难理解。关键是str_a和str_b为什么特么是一样的啊?如果按照上面的分析,这两个肯定是不一样的,返回的应该是false啊,这里需要着重说明一下,==操作符在string中被重写了,这也是为什么我在上面强调是在没有重写的前提条件下。也就是说字符串的==操作符比较的是变量栈空间上的值(地址咯)所对应的堆空间上的值(常说的字符串的值),有点拗口啊,慢慢体会吧!或者说比较的时候,你简单记忆为字符串与值类型一样吧(实际不是一样的,只是重写罢了!内部调用的是Equals方法)
这里写图片描述

4.Equals

Equals比较的是什么(没有重写的前提下)?网上的说法大都是:对于值类型Equals比较的是值是否相等,对引用类型Equals比较的是引用对象的值。参照上一节的对于==的理解,我是这样描述的:对于值类型Equals比较的是栈空间中变量的值,对于引用类型Equals比较的是堆空间中引用对象的值。和上面一样,看看这张图吧!
这里写图片描述

        static void Main(string[] args)
        {
            int int_a = 25;
            int int_b = 25;
            Console.WriteLine(int_a.Equals(int_b));

            Programmer p_a = new Programmer("WUC", 25, "421127199205175214", new List<string>() { "C#", "Pyhton" });
            Programmer p_b = new Programmer("WUC", 25, "421127199205175214", new List<string>() { "C#", "Pyhton" });
            Programmer p_c = new Programmer("WPY", 25, "421127199205175214", new List<string>() { "C#", "Pyhton" });

            Console.WriteLine(p_a.Equals(p_b));
            Console.WriteLine(p_a.Equals(p_c));

            Console.ReadKey();
        }

这里写图片描述
说明:p_a和p_c Equals不相等这个好理解,关键是p_a和p_b为什么不相等啊,不是说好的引用类型比对的是堆空间的值吗?值是相等的啊,原因就在于在Equals是Object中的一个虚方法,而person类中没有对她进行重写,因此此时调用的仍是父类中的Equals方法。但是父类是无法知道你都有哪些成员字段的,因此返回的是false,不信的话我们来重写试试啊!

        public override bool Equals(object obj)
        {
            if (obj is Programmer)
            {
                Programmer equalsObj = obj as Programmer;
                bool temp_result_a = equalsObj.Name == this.Name && equalsObj.Age == this.Age && equalsObj.IdCardNum == this.IdCardNum;
                bool temp_result_b = true;
                if (equalsObj.Language.Count != this.Language.Count) temp_result_b = false;
                equalsObj.Language.Sort();
                this.Language.Sort();
                for (int i = 0; i < equalsObj.Language.Count; i++) 
                {
                    if (equalsObj.Language[i] != this.Language[i]) 
                    {
                        temp_result_b = false;
                        break;
                    }
                }
                return temp_result_a && temp_result_b;
            }
            return false;
        }

这里写图片描述
有一个问题需要提出,为什么不这样重写:

        public override bool Equals(object obj)
        {
            if(obj is Programmer)
            {
                Programmer equalsObj = obj as Programmer;
                return equalsObj.Name == this.Name && equalsObj.Age == this.Age && equalsObj.IdCardNum == this.IdCardNum
                    && equalsObj.Language == this.Language;
            }
            return false;
        }

重写中借用到了==,而上一节中已说明==对于引用类型比较的是栈空间的地址,而Language是string类型的List泛型集合,显然他们的地址时不一样的,结果还是达不到比较”内容”的目的,因此重写的时候挨个去判断了一下。

5.object.ReferenceEquals

作为object的静态方法ReferenceEquals()也可以用来比较相等,这里直接看看他的源码就一目了然了:
这里写图片描述
借助==实现的,那么对于值类型来说装箱操作之后变成object引用类型,按照上面的==的相等比较解释:==始终比较的是栈空间的值(包括地址),显然装箱之后的值是地址,而且也不一样啊,所以对于值类型的ReferenceEquals始终返回的是false,对于引用类型来说则比较的是栈空间中引用地址是否相等。

        static void Main(string[] args)
        {
            int int_a = 25;
            int int_b = 25;
            Console.WriteLine(object.ReferenceEquals(int_a, int_b));

            Programmer p_a = new Programmer("WUC", 25, "421127199205175214", new List<string>() { "C#", "Pyhton" });
            Programmer p_b = new Programmer("WUC", 25, "421127199205175214", new List<string>() { "C#", "Pyhton" });
            Programmer p_c = p_b;

            Console.WriteLine(object.ReferenceEquals(p_a, p_b));
            Console.WriteLine(object.ReferenceEquals(p_b, p_c));

            Console.ReadKey();
        }

这里写图片描述

6.三者之间的区别

与其说是三者之间的区别还不如说是两者之间的区别,ReferenceEquals是借助==来实现的,所以对于本文来说只需要你搞清楚==和Equals的区别就可以了,其他的基本上都是借助两者来实现的。上面已经说的很详细了,这里谈及区别也不想多做赘述,直接分析几个案例吧,从代码出发直观简单:

  • 案例1
        static void Main(string[] args)
        {
            string str_a = new string(new char[] { 'f', 'u', 'c', 'k' });
            string str_b = new string(new char[] { 'f', 'u', 'c', 'k' });
            string str_c = "fuck";
            string str_d = "fuck";

            Console.WriteLine(str_a == str_b);
            Console.WriteLine(str_a.Equals(str_b));
            Console.WriteLine(object.ReferenceEquals(str_a, str_b));

            Console.WriteLine(str_a == str_c);
            Console.WriteLine(str_a.Equals(str_c));
            Console.WriteLine(object.ReferenceEquals(str_a, str_c));

            Console.WriteLine(str_c == str_d);
            Console.WriteLine(str_c.Equals(str_d));
            Console.WriteLine(object.ReferenceEquals(str_c, str_d));

            Console.ReadKey();
        }

这里写图片描述
这里写图片描述
说明:str_a和str_b直接在堆空间中开辟内存,他们的引用地址显然是不一样的,但是由于字符串的驻留机制,给str_d赋值时会去堆空间中找,有没有之前创建过的”wuc”,找到了一个之前由str_c创建的”wuc”,就不会在浪费空间了,直接将它的地址返回给str_d,因此str_d的地址和str_c是一样的哦!
解释:还是那句话,字符串重写了==和Equals,直接去比较堆空间的值,因此==和equals的比较在上面的案例中均为true,但是ReferenceEquals没有重写(也不重写),因此ReferenceEquals源码中的实现时使用到的==依然按照愿意去解释,即是是比较栈空间的值(包括地址),显然四者之间只有str_c与str_d是相同的值(地址)

  • 案例2
        static void Main(string[] args)
        {
            object obj_a = 1;
            object obj_b = 1;

            Console.WriteLine(obj_a == obj_b);
            Console.WriteLine(obj_a.Equals(obj_b));
            Console.WriteLine(ReferenceEquals(obj_a, obj_b));

            Console.ReadKey();
        }

这里写图片描述
说明:object引用类型,参照上面的变量存储图,显然栈空间的值(地址)是不一样的,==操作结果为false,而对空间的值是相等的,都为1,Equals操作结果为true,至于说ReferenceEquals,它和==的操作结果一样哦(内部利用==实现的)。

再次重申一下:==本意是比较栈空间中变量的值的,只不过这个值可能是整型、浮点型什么的,也可以是指向堆空间的地址;而Equals本意是比较堆空间中的”内容”,如果没有重写,则是按照object的Equals原则返回比对结果值。这之中的字符串例外,它把Equals和==都重写了,都改成了直接去堆空间中比较内容了,最后一个ReferenceEquals,不能重写,对于任何的值类型均返回false,对于引用类型按照==的原则去返回结果值,即是比对栈空间里的值(包括地址)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值