C#中==運算符

来源:http://www.cnblogs.com/tdfblog/archive/2017/06/22/About-Equality-Operator-in-NET.html
-Advertisement-
Play Games

在這篇博客中,我們將介紹如下內容: ==運算符與基元類型 ==運算符與引用類型 ==運算符與String類型 ==運算符與值類型 ==運算符與泛型 ==運算符與基元類型 我們分別用兩種方式比較兩個整數,第一個使用的是Equals(int)方法,每二個使用的是==運算符: 運行上面的示例,兩個語句出的 ...


  在這篇博客中,我們將介紹如下內容:

  • ==運算符與基元類型
  • ==運算符與引用類型
  • ==運算符與String類型
  • ==運算符與值類型
  • ==運算符與泛型

 

==運算符與基元類型

  我們分別用兩種方式比較兩個整數,第一個使用的是Equals(int)方法,每二個使用的是==運算符:  

 1 class Program
 2 {
 3     static void Main(String[] args)
 4     {
 5         int num1 = 5;
 6         int num2 = 5;
 7 
 8         Console.WriteLine(num1.Equals(num2));
 9         Console.WriteLine(num1 == num2);
10     }
11 }

  運行上面的示例,兩個語句出的結果均為true。我們通過ildasm.exe工具進行反編譯,查看IL代碼,瞭解底層是如何執行的。

  

  如果您以前從來沒有接觸過IL指令,不過沒關係,在這裡您不需要理解所有的指令,我們只是想瞭解這兩個比較方式的差異。

  您可以看到這樣一行代碼:

1   IL_0008:  call       instance bool [mscorlib]System.Int32::Equals(int32)

  在這裡調用的是int類型Equals(Int32)方法(該方法是IEquatable<Int>介面的實現)。

  現在再來看看使用==運算符比較生成的IL指令:

1   IL_0015:  ceq

  您可以看到,==運行符使用的是ceq指令,它是使用CPU寄存器來比較兩個值。C#==運算符底層機制是使用ceq指令對基元類型進行比較,而不是調用Equals方法。

 

==運算符與引用類型

  修改上面的示例代碼,將int類型改為引用類型,編譯後通過ildasm.exe工具反編譯查看IL代碼。

 1 class Program
 2 {
 3     static void Main(String[] args)
 4     {
 5         Person p1 = new Person();
 6         p1.Name = "Person1";
 7 
 8         Person p2 = new Person();
 9         p2.Name = "Person1";
10 
11         Console.WriteLine(p1.Equals(p2));
12         Console.WriteLine(p1 == p2);
13     }
14 }

  上述C#代碼的IL代碼如下所示: 

  

  我們看到p1.Equals(p2)代碼,它是通過調用Object.Equals(Object)虛方法來比較相等,這是在意料之中的事情;現在我們來看==運算符生成的IL代碼,與基元類型一致,使用的也是ceq指令。

 

==運算符與String類型

   接來下來看String類型的例子:  

 1 class Program
 2 {
 3     static void Main(String[] args)
 4     {
 5         string s1 = "Sweet";
 6         string s2 = String.Copy(s1);
 7 
 8         Console.WriteLine(ReferenceEquals(s1, s2));
 9         Console.WriteLine(s1 == s2);
10         Console.WriteLine(s1.Equals(s2));
11     }
12 }

  上面的代碼與我們以前看過的非常相似,但是這次我們使用String類型的變數。我們建一個字元串,並付給s1變數,在下一行代碼我們創建這個字元串的副本,並付給另一個變數名稱s2

  運行上面的代碼,在控制台輸出的結果如下:

  

  您可以看到ReferenceEquals返回false,這意味著這兩個變數是不同的實例,但是==運算符和Equals方法返回的均是true。在String類型中,==運算符執行的結果與Equals執行的結果一樣。

  同樣我們使用過ildasm.exe工具反編譯查看生成IL代碼。

  

  在這裡我們沒有看到ceq指令,對String類型使用==運算符判斷相等時,調用的是一個op_equality(string,string)的新方法,該方法需要兩個String類型的參數,那麼它到底是什麼呢?

  答案是String類型提供了==運算符的重載。在C#中,當我們定義一個類型時,我們可以重載該類型的==運算符;例如,對於以前的例子中我們實現的Person類,如果我們為它重載==運算符,大致的代碼如下:

 1 public class Person
 2 {
 3 
 4     public string Name { get; set; }
 5 
 6     public static bool operator ==(Person p1, Person p2)
 7     {
 8         // 註意這裡不能使用==,否則會導致StackOverflowException
 9         if (ReferenceEquals(p1, p2))
10             return true;
11 
12         if (ReferenceEquals(p1, null) || ReferenceEquals(p2, null)) 
13             return false; 
14 
15           return p1.Name == p2.Name;
16     }
17 
18     public static bool operator !=(Person p1, Person p2)
19     {
20         return !(p1 == p2);
21     }
22 }

  上面的代碼很簡單,我們實現了==運算符重載,這是一個靜態方法,但這裡要註意的是,方法的名稱是perator ==,與靜態方法的相似性;事實上,它們會被由編譯器成一個名稱為op_Equality()的特殊靜態方法。

  為了使用事情更加清楚,我們查看微軟實現的String類型。

  

  在上面的截圖中,我們可以看到,有兩個運算符的重載,一個用於相等,另一個是不等式運算符,其運算方式完全相同,但是否定等於運算符輸出。需要註意的一點是,如果您想重載一個類型的==運行符的實現,那麼您還需要重載!=操作符的實現,否則編譯會報錯。

 

==運算符與值類型

   在演示值類型的示例前,我們先將Person類型從引用類型改為值類型,Person定義如下:

 1 public struct Person
 2 {
 3     public string Name { get; set; }
 4 
 5     public Person(string name)
 6     {
 7         Name = name;
 8     }
 9 
10     public override string ToString()
11     {
12 
13         return Name;
14     }
15 }

  我們將示例代碼改為如下:

 1  class Program
 2  {
 3      static void Main(String[] args)
 4      {
 5          Person p1 = new Person("Person1");
 6          Person p2 = new Person("Person2");
 7 
 8          Console.WriteLine(p1.Equals(p2));
 9          Console.WriteLine(p1 == p2);
10      }
11  }

   當我們在嘗試編譯上述代碼時,VS將提示如下錯誤:

  根據錯誤提示,我們需要實現Person結構體的==運算符重載,重載的語句如下(忽略具體的邏輯):

1  public static bool operator ==(Person p1, Person p2)
2  {
3  }
4  public static bool operator !=(Person p1, Person p2)
5  {
6  }

   當添加上面代碼後,重新編譯程式,通過ildasm.exe工具反編譯查看IL代碼,發現值類型==運算符調用也是op_Equality方法。

  關於值類型,我們還需要說明一個問題,在不重寫Equals(object)方法時,該方法實現的原理是通過反射遍歷所有欄位並檢查每個欄位的相等性,關於這一點,我們不演示;對於值類型,最好重寫該方法。

 

==運算符與泛型

  我們編寫另一段示例代碼,聲明兩個String類型變數,通過4種不同的方式比較運算:

 1 public class Program
 2 {
 3     public static void Main(string[] args)
 4     {
 5         string str = "Sweet";
 6         string str1 = string.Copy(str);
 7 
 8         Console.WriteLine(ReferenceEquals(str, str1));
 9         Console.WriteLine(str.Equals(str1));
10         Console.WriteLine(str == str1);
11         Console.WriteLine(object.Equals(str, str1));
12     }
13 }

  輸出的結果如下:

  

  首先,我們使用ReferenceEquals方法判斷兩個String變數都引用相同,接下來我們再使用實例方法Equals(string),在第三行,我們使用==運算符,最後,我們使用靜態方法Object.quals(object,object)(該方法最終調用的是String類型重寫的Object.Equals(object)方法)。我們得到結論是:

  • ReferenceEquals方法返回false,因為它們不是同一個對象的引用;
  • String類型的Equals(string)方法返回也是true,因為兩個String類型是相同的(即相同的序列或字元);
  • ==運算符也將返回true,因為這兩個String類型的值相同的;
  • 虛方法Object.Equals也將返回true,這是因為在String類型重寫了方法,判斷的是String是否值相同。

  現在我們來修改一下這個代碼,將String類型改為Object類型:

 1 public class Program
 2 {
 3     public static void Main(string[] args)
 4     {
 5         object str = "Sweet";
 6         object str1 = string.Copy((string)str);
 7 
 8         Console.WriteLine(ReferenceEquals(str, str1));
 9         Console.WriteLine(str.Equals(str1));
10         Console.WriteLine(str == str1);
11         Console.WriteLine(object.Equals(str, str1));
12     }
13 }

 

  運行的結果如下:

  

  第三種方法返回的結果與修改之前不一致,==運算符返回的結果是false,這是為什麼呢?

  這是因為==運算符實際上是一個靜態的方法,對一非虛方法,在編譯時就已經決定用調用的是哪一個方法。在上面的例子中,引用類型使用的是ceq指令,而String類型調用是靜態的op_Equality方法;這兩個實例不是同一個對象的引用,所以ceq指令執行後的結果是false

  再來說一下==運算符與泛型的問題,我們創建一個簡單的方法,通過泛型方法判斷兩個泛型參數是否相等併在控制臺上列印出結果:

1 static void Equals<T>(T a, T b)
2 {
3     Console.WriteLine(a == b);
4 }

  但是當我們編譯這段代碼時,VS提示如下錯誤:

  上面顯示的錯誤很簡單,不能使用==運算符比較兩個泛型T。因為T可以是任何類型,它可以是引用類型、值類型,不能提供==運算符的具體實現。

  如果像下麵這樣修改一下代碼:

1 static void Equals<T>(T a, T b) where T : class
2 {
3     Console.WriteLine(a == b);
4 }

  當我們將泛型類型T改為引用類型,能成功編譯;修改Main方法中的代碼,創建兩個相同的String類型,和以前的例子一樣:  

 1 public class Program
 2 {
 3     static void Main(string[] args)
 4     {
 5         string str = "Sweet";
 6         string str1 = string.Copy(str);
 7 
 8         Equals(str, str1);
 9     }
10 
11     static void Equals<T>(T a, T b) where T : class
12     {
13         Console.WriteLine(a == b);
14     }
15 }

 

  輸出的結果如下:  

  

  結果與您預期的結果不一樣吧,我們期待的結果是true,輸出的結果是false。不過仔細思考一下,也許會找到答案,因為泛型的約束是引用類型,==運算符對於引用類型使用的是引用相等,IL代碼可以證明這一點:

  

  如果我們泛型方法中的==運算符改為使用Equals方法,代碼如下:  

1 static void Equals<T>(T a, T b)
2 {
3     Console.WriteLine(object.Equals(a, b));
4 }

   我們改用Equals,也可以去掉class約束;如果我們再次運行代碼,控制台列印的結果與我們預期的一致,這是因為調用是虛方法object.Equals(object)重寫之後的實現。

  但是其它的問題來了,如果對於值類型,這裡就會產生裝箱,有沒有解決的辦法呢?關於這一點,我們直接給出答案,有時間專門來討論這個問題。

  將比較的值類型實現IEquatable<T>介面,並將比較的代碼改為如下,這樣可以避免裝箱:

1 static void Equals<T>(T a, T b)
2 {
3     Console.WriteLine(EqualityComparer<T>.Default.Equals(a, b));
4 }

 

總結

  對於基元類型==運算符的底層機制使用的是ceq指令,通過CPU寄存器進行比較;

  對於引用類型==運算符,它也使用的ceq指令來比較記憶體地址;

  對於重載==運算符的類型,實際上調用的是op_equality這個特殊的方法;

  儘量保證==操作符重載和Object.Equals(Object)虛方法的寫返回的是相同的結果;

  對於值類型,Equals方法預設是通過反射遍歷所有欄位並檢查每個欄位的相等性,為了提高性能,我們需要重寫該方法;

  值類型預設情況下不能使用==運算符,需要實現==運算符的重載;

  由於==運算符重載實現實際上是一個靜態的方法,在泛型類或方法中使用時與實際的結果可能存在差別,使用Equals方法可以避免這個問題。

  

  轉載請註明出自,原文鏈接:http://www.cnblogs.com/tdfblog/p/About-Equality-Operator-in-NET.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 6月1日,《中華人民共和國網路安全法》(簡稱《網路安全法》)開始正式實施,這是我國第一部全面規範網路空間安全管理方面問題的基礎性法律。它的頒佈,讓許多互聯網企業以及廣大民眾倍感欣喜。 進入網路時代後,互聯網應用行業發展迅猛,成為推動經濟發展和社會進步的重要力量。然而,發展的同時也帶來了許多新的問題和 ...
  • 一些介紹 CodeFirst是EntityFrameworks的一種開發模式,即代碼優先,它以業務代碼為主,通過代碼來生成資料庫,並且加上migration的強大數據表比對功能來生成資料庫版本,讓程式開發人員不用維護資料庫的變更,而直接維護migration即可,在它裡面有你當前版本和過去歷史版本的 ...
  • // 1. asp頁面使用EasyUI框架需要的Css樣式和JS <script src="../script/jquery-easyui-1.4.5/jquery.min.js" type="text/javascript" charset = "utf-8"></script> <script ...
  • 把一個 dic綁定到了listview上,有時候下拉列表會報這個異常。因為直接使用了itemssource = dic,而dic在另一個線程上不定期更新,這樣如果直接綁定的話就會報這個錯誤,原因是直接綁定的話會把itemssource的記憶體地址直接指向dic的記憶體地址,當dic更新後,會導致記憶體地址 ...
  • 訂單系統設計 總體設計 1.每次下單時間少於3秒 2.庫存驗證不存在多買的情況 3.訂單能夠按照不同供應商進程拆分 4. 物流信息能夠回傳 訂單狀態機設計 1.待系統審核 2.待支付 3.待發貨 4.待簽收 5.已完成 6.訂單關閉 訂單狀態流轉如下圖示: 1)審核失敗 2)未支付(待支付24小時) ...
  • 1.根據單個分隔字元用split截取 例如 即可得到sArray[0]="GT123",sArray[1]="1"; 2.利用多個字元來分隔字元串 例如 得到sArray[0]="GTAZB",sArray[1]="Jiang",sArray[2]="Ben",sArray[3]="123"; 3根 ...
  • 在前端 UI 開發中,有時,我們會遇到這樣的需求:在一個 ScrollViewer 中有很多內容,而我們需要實現在執行某個操作後能夠定位到其中指定的控制項處;這很像在 HTML 頁面中點擊一個鏈接後定位到當前網頁上的某個 anchor。 要實現它,首先我們需要看 ScrollViewer 為我們提供的 ...
  • TypeInfo,PropertyInfo,MethodInfo,FieldInfo ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...