浅析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该方法以实现快速判等;而==操作符默认情况下不可用,若想用需自己实现。

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

posted @ 2018-08-26 15:46  Only雪里梅  阅读(503)  评论(0编辑  收藏  举报