• 浅析c#中==操作符和equals方法


      在之前的文章中,我们讲到了使用C#中提供的Object类的虚Equals方法来判断Equality,但实际上它还提供了另外一种判断Equality的方法,那就是使用==运算符。许多童鞋也许会想当然的认为==不过是Equals方法的语法糖而已,然而事实却并非如此。尽管从实现上来说,它给出的判定结果往往和Object.Equals方法一致,但这不是必须的,因为两者的实现机制完全相同。下面就让我们看看两者的区别。

    1、==和基元类型

      让我们首先从最基础的基元类型,如int、float、double等开始,看看==是如何对他们进行相等性测试的。

    class Program
    {
        static void Main(String[] args)
        {
            int number1 = 9;
            int number2 = 9;
    
            Console.WriteLine(number1.Equals(number2));
            Console.WriteLine(number1 == number2);
    
            Console.Read();
        }
    }
    

       上面的代码中,我们比较两个整数的相等性,其中用到了两种比较方式,第一种调用Int32类型对Object.Equals方法的重载版本,第二种调用==运算符。两种比较方法的结果都显示true,似乎两者的实现机制都是调用的Equals方法进行比较一样。下面让我们使用ildasm.exe实用程序来验证一下这个猜想是否正确吧。 

      上面这条IL语句对应于源码中的第一个比较方式,可以看到它是通过调用Object.Equals实现的,该方法的定义位于System.Int32类型中,并且是实现了IEquatable<int>接口。

      下面来看==操作符对应的IL语句。    

      可以看到,==操作符生成的IL并没有调用Object.Equals,而是生成了一条ceq指令,该指令的作用是比较加载到栈上的两个值并且是通过CPU寄存器进行相等性比较的。

      总结:对于基元类型的相等性判断而言,C#中==操作符是通过ceq指令实现的,而非Object.Equals方法。

    2、==和引用类型

      接下来让我们看下==如何对引用类型进行相等性判定。

    static void Main(string[] args)
            {
                Customer C1 = new Customer();
                C1.FirstName = "Si";
                C1.LastName = "Li";
                Customer C2 = new Customer();
                C2.FirstName = "Si";
                C2.LastName = "LI";
                Console.WriteLine(C1.Equals(C2));
           Console.WriteLine(C1==C2); Console.Read(); } public class Customer { public string FirstName { get; set; } public string LastName { get; set; } } 

      运行上面的代码,结果会显示两个False。这不由得让我们猜测:==运算符和Equals方法一样,也是作引用相等性判定,而不是值相等性判定。接下来让我们通过ildasm.exe查看IL代码确认一下。

      可以看到,对于C1.Equals(C2)而言,它生成的IL代码,调用的是Object.Equals方法,由于没有重载,故默认进行引用相等性测试。而对于C1==C2,它对于生成的IL代码,仅是一条ceq指令,这里用来判定引用相等性。

      也许有童鞋好奇==是如何作引用相等性判定的。这是因为引用类型的变量持有的是同类型的实例对象的内存地址,而内存地址不过是一个数字,因此可以用ceq判等性测试,就像对整数类型的判等测试一样。

      总结:对于引用类型的判等而言,==操作符是通过ceq指令比较内存地址来实现的。

    3、==和String类型

      同上,还是通过一段简单的代码测试==如何对String类型作相等性测试。

    class Program
    { 
        static void Main(String[] args)
        { 
            string str1 =  "hello";
            string str2 = String.Copy(str1);
     
            Console.WriteLine(ReferenceEquals(str1, str2));
            Console.WriteLine(str1 == str2);
            Console.WriteLine(str1.Equals(str2));
                
            Console.Read(); 
        } 
    }
    

       测试以上代码,结果显示:False True True。答案已经很明朗了,==操作符对String类型作值相等性测试。下面通过IL代码揭秘一下==的运行机制。

      从以上的IL代码片段中,我们并未发现ceq指令,而是调用了一个op_equality(string, string)方法。那么该方法是如何产生的呢?实际上,这是由于String类重载了==运算符导致的。在C#中,如果重载了==运算符,那么编译器就会编译生成一个static方法,名字为op_equality。

      若在VS中通过导航到定义来查看String类的源代码,会发现String类实现了两个运算符重载方法,一个是相等性测试,另一个是不等性测试。

      当我们为自己的类型重载==操作符的实现时,应该牢记一点:为了通过编译,应为==和!=同时提供重载实现。

      总结:

      1、通过上面的学习,我们知道对于引用类型,==操作符会以下面两种方式之一进行判等测试。

      • 若存在==的重载实现,那么编译器将把它编译为一个static方法。
      • 若不存在==的重载实现,编译器将它编译为一条ceq指令,比较内存地址。

        2、当我们更改一个类型的判等逻辑时,应该同时为Equals方法和==提供实现,并且应保证两者的比较结果一致,否则,使用该类型的开发人员将感到困惑。

     4、==和值类型

      通过上面的学习,我们已经晓得==如何对基元类型和引用类型作判等测试。但还未提及非基元值类型,下面让我们看看==操作符如何

      还是以上面提到的Customer类为例,只是这次我们将它变更为struct。代码如下:

    static void Main(string[] args)
            {
                Customer C1 = new Customer();
                C1.FirstName = "Si";
                C1.LastName = "Li";
                Customer C2 = new Customer();
                C2.FirstName = "Si";
                C2.LastName = "LI";
                Console.WriteLine(C1.Equals(C2));
        Console.WriteLine(C1==C2);
                Console.Read();
            }
            public struct Customer
            {
                public string FirstName { get; set; }
                public string LastName { get; set; }
            } 

      若运行上面的程序,会产生如下的编译错误:

      该错误清楚地向我们指明:不存在用于非基元值类型的重载==操作符。若要使用==进行判等,必须由我们提供。现在让我们在Customer结构的定义中添加以下代码:

     public static bool operator ==(Customer p1, Customer p2)
            {
            }
    

       此时,Main方法中的代码就不会产生编译错误了,但是该程序仍不能编译通过,因为operator ==方法的返回值类型为bool,而我们并没有提供任何返回值。但这并不重要,我们仅仅在这里探讨了应为非基元值类型提供==操作符的重载实现,具体的实现代码根据自己的业务需求填充就行了。

    5、总结

      下面让我们来总结一下==和Equals方法对各种类型进行判等性测试的逻辑:

    • 对于基元类型,比如intfloatlongbool等,两者均比较值,故比较结果一致。
    • 对于大多数引用类型而言,==和Object.Equals方法均默认比较引用,但可以选择重载==和Equals方法,但为了不使该类型的使用者感到困惑,应同时overload或override两者,并使两者的判定结果保持一致。
    • 对于非基元值类型而言,Object.Equals方法将通过反射进行值相等性测试,但它的性能低下,实践中最好override该方法以实现快速判等;而==操作符默认情况下不可用,若想用需自己实现。

      由于==操作符比较简洁,因此在平常开发中更受喜爱,但它也存在缺陷,我将在下篇博文中进行介绍。

  • 相关阅读:
    ApacheServer-----关于443端口被占用的解决方法
    《小强与小明》——正在疯传的伟大的故事
    不争万年,只珍朝夕------我的态度
    dubbo搭建
    使用netty的第一个Hello World
    myBatis数据库常用标签
    mysql 索引
    tomcat部署项目的一点心得
    【康托展开】
    初探计算机硬盘
  • 原文地址:https://www.cnblogs.com/lian--ying/p/9536392.html
Copyright © 2020-2023  润新知