1. 程式人生 > >淺析c#中==操作符和equals方法

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

邏輯 mce 需求 ram margin width 通過 否則 可用

  在之前的文章中,我們講到了使用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方法對各種類型進行判等性測試的邏輯:

  • 對於基元類型,比如int, float, long, bool等,兩者均比較值,故比較結果一致。
  • 對於大多數引用類型而言,==和Object.Equals方法均默認比較引用,但可以選擇重載==和Equals方法,但為了不使該類型的使用者感到困惑,應同時overload或override兩者,並使兩者的判定結果保持一致。
  • 對於非基元值類型而言,Object.Equals方法將通過反射進行值相等性測試,但它的性能低下,實踐中最好override該方法以實現快速判等;而==操作符默認情況下不可用,若想用需自己實現。

  由於==操作符比較簡潔,因此在平常開發中更受喜愛,但它也存在缺陷,我將在下篇博文中進行介紹。

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