C#中的==運(yùn)算符
在這篇文章中,我們將介紹如下內(nèi)容:
- ==運(yùn)算符與基元類(lèi)型
- ==運(yùn)算符與引用類(lèi)型
- ==運(yùn)算符與String類(lèi)型
- ==運(yùn)算符與值類(lèi)型
- ==運(yùn)算符與泛型
==運(yùn)算符與基元類(lèi)型
我們分別用兩種方式比較兩個(gè)整數(shù),第一個(gè)使用的是Equals(int)方法,每二個(gè)使用的是==運(yùn)算符:
class Program { static void Main(String[] args) { int num1 = 5; int num2 = 5; Console.WriteLine(num1.Equals(num2)); Console.WriteLine(num1 == num2); } }
運(yùn)行上面的示例,兩個(gè)語(yǔ)句出的結(jié)果均為true。我們通過(guò)ildasm.exe工具進(jìn)行反編譯,查看IL代碼,了解底層是如何執(zhí)行的。
如果您以前從來(lái)沒(méi)有接觸過(guò)IL指令,不過(guò)沒(méi)關(guān)系,在這里您不需要理解所有的指令,我們只是想了解這兩個(gè)比較方式的差異。
您可以看到這樣一行代碼:
IL_0008: call instance bool [mscorlib]System.Int32::Equals(int32)
在這里調(diào)用的是int類(lèi)型Equals(Int32)方法(該方法是IEquatable<Int>接口的實(shí)現(xiàn))。
現(xiàn)在再來(lái)看看使用==運(yùn)算符比較生成的IL指令:
IL_0015: ceq
您可以看到,==運(yùn)行符使用的是ceq指令,它是使用CPU寄存器來(lái)比較兩個(gè)值。C#==運(yùn)算符底層機(jī)制是使用ceq指令對(duì)基元類(lèi)型進(jìn)行比較,而不是調(diào)用Equals方法。
==運(yùn)算符與引用類(lèi)型
修改上面的示例代碼,將int類(lèi)型改為引用類(lèi)型,編譯后通過(guò)ildasm.exe工具反編譯查看IL代碼。
class Program { static void Main(String[] args) { Person p1 = new Person(); p1.Name = "Person1"; Person p2 = new Person(); p2.Name = "Person1"; Console.WriteLine(p1.Equals(p2)); Console.WriteLine(p1 == p2); } }
上述C#代碼的IL代碼如下所示:
我們看到p1.Equals(p2)代碼,它是通過(guò)調(diào)用Object.Equals(Object)虛方法來(lái)比較相等,這是在意料之中的事情;現(xiàn)在我們來(lái)看==運(yùn)算符生成的IL代碼,與基元類(lèi)型一致,使用的也是ceq指令。
==運(yùn)算符與String類(lèi)型
接來(lái)下來(lái)看String類(lèi)型的例子:
class Program { static void Main(String[] args) { string s1 = "Sweet"; string s2 = String.Copy(s1); Console.WriteLine(ReferenceEquals(s1, s2)); Console.WriteLine(s1 == s2); Console.WriteLine(s1.Equals(s2)); } }
上面的代碼與我們以前看過(guò)的非常相似,但是這次我們使用String類(lèi)型的變量。我們建一個(gè)字符串,并付給s1變量,在下一行代碼我們創(chuàng)建這個(gè)字符串的副本,并付給另一個(gè)變量名稱(chēng)s2。
運(yùn)行上面的代碼,在控制臺(tái)輸出的結(jié)果如下:
您可以看到ReferenceEquals返回false,這意味著這兩個(gè)變量是不同的實(shí)例,但是==運(yùn)算符和Equals方法返回的均是true。在String類(lèi)型中,==運(yùn)算符執(zhí)行的結(jié)果與Equals執(zhí)行的結(jié)果一樣。
同樣我們使用過(guò)ildasm.exe工具反編譯查看生成IL代碼。
在這里我們沒(méi)有看到ceq指令,對(duì)String類(lèi)型使用==運(yùn)算符判斷相等時(shí),調(diào)用的是一個(gè)op_equality(string,string)的新方法,該方法需要兩個(gè)String類(lèi)型的參數(shù),那么它到底是什么呢?
答案是String類(lèi)型提供了==運(yùn)算符的重載。在C#中,當(dāng)我們定義一個(gè)類(lèi)型時(shí),我們可以重載該類(lèi)型的==運(yùn)算符;例如,對(duì)于以前的例子中我們實(shí)現(xiàn)的Person類(lèi),如果我們?yōu)樗剌d==運(yùn)算符,大致的代碼如下:
public class Person { public string Name { get; set; } public static bool operator ==(Person p1, Person p2) { // 注意這里不能使用==,否則會(huì)導(dǎo)致StackOverflowException if (ReferenceEquals(p1, p2)) return true; if (ReferenceEquals(p1, null) || ReferenceEquals(p2, null)) return false; return p1.Name == p2.Name; } public static bool operator !=(Person p1, Person p2) { return !(p1 == p2); } }
上面的代碼很簡(jiǎn)單,我們實(shí)現(xiàn)了==運(yùn)算符重載,這是一個(gè)靜態(tài)方法,但這里要注意的是,方法的名稱(chēng)是perator ==,與靜態(tài)方法的相似性;事實(shí)上,它們會(huì)被由編譯器成一個(gè)名稱(chēng)為op_Equality()的特殊靜態(tài)方法。
為了使用事情更加清楚,我們查看微軟實(shí)現(xiàn)的String類(lèi)型。
在上面的截圖中,我們可以看到,有兩個(gè)運(yùn)算符的重載,一個(gè)用于相等,另一個(gè)是不等式運(yùn)算符,其運(yùn)算方式完全相同,但是否定等于運(yùn)算符輸出。需要注意的一點(diǎn)是,如果您想重載一個(gè)類(lèi)型的==運(yùn)行符的實(shí)現(xiàn),那么您還需要重載!=操作符的實(shí)現(xiàn),否則編譯會(huì)報(bào)錯(cuò)。
==運(yùn)算符與值類(lèi)型
在演示值類(lèi)型的示例前,我們先將Person類(lèi)型從引用類(lèi)型改為值類(lèi)型,Person定義如下:
public struct Person { public string Name { get; set; } public Person(string name) { Name = name; } public override string ToString() { return Name; } }
我們將示例代碼改為如下:
class Program { static void Main(String[] args) { Person p1 = new Person("Person1"); Person p2 = new Person("Person2"); Console.WriteLine(p1.Equals(p2)); Console.WriteLine(p1 == p2); } }
當(dāng)我們?cè)趪L試編譯上述代碼時(shí),VS將提示如下錯(cuò)誤:
根據(jù)錯(cuò)誤提示,我們需要實(shí)現(xiàn)Person結(jié)構(gòu)體的==運(yùn)算符重載,重載的語(yǔ)句如下(忽略具體的邏輯):
public static bool operator ==(Person p1, Person p2) { } public static bool operator !=(Person p1, Person p2) { }
當(dāng)添加上面代碼后,重新編譯程序,通過(guò)ildasm.exe工具反編譯查看IL代碼,發(fā)現(xiàn)值類(lèi)型==運(yùn)算符調(diào)用也是op_Equality方法。
關(guān)于值類(lèi)型,我們還需要說(shuō)明一個(gè)問(wèn)題,在不重寫(xiě)Equals(object)方法時(shí),該方法實(shí)現(xiàn)的原理是通過(guò)反射遍歷所有字段并檢查每個(gè)字段的相等性,關(guān)于這一點(diǎn),我們不演示;對(duì)于值類(lèi)型,最好重寫(xiě)該方法。
==運(yùn)算符與泛型
我們編寫(xiě)另一段示例代碼,聲明兩個(gè)String類(lèi)型變量,通過(guò)4種不同的方式比較運(yùn)算:
public class Program { public static void Main(string[] args) { string str = "Sweet"; string str = string.Copy(str); Console.WriteLine(ReferenceEquals(str, str1)); Console.WriteLine(str.Equals(str1)); Console.WriteLine(str == str1); Console.WriteLine(object.Equals(str, str1)); } }
輸出的結(jié)果如下:
首先,我們使用ReferenceEquals方法判斷兩個(gè)String變量都引用相同,接下來(lái)我們?cè)偈褂脤?shí)例方法Equals(string),在第三行,我們使用==運(yùn)算符,最后,我們使用靜態(tài)方法Object.quals(object,object)(該方法最終調(diào)用的是String類(lèi)型重寫(xiě)的Object.Equals(object)方法)。我們得到結(jié)論是:
ReferenceEquals方法返回false,因?yàn)樗鼈儾皇峭粋€(gè)對(duì)象的引用;
String類(lèi)型的Equals(string)方法返回也是true,因?yàn)閮蓚€(gè)String類(lèi)型是相同的(即相同的序列或字符);
==運(yùn)算符也將返回true,因?yàn)檫@兩個(gè)String類(lèi)型的值相同的;
虛方法Object.Equals也將返回true,這是因?yàn)樵赟tring類(lèi)型重寫(xiě)了方法,判斷的是String是否值相同。
現(xiàn)在我們來(lái)修改一下這個(gè)代碼,將String類(lèi)型改為Object類(lèi)型:
public class Program { public static void Main(string[] args) { object str = "Sweet"; object str = string.Copy((string)str); Console.WriteLine(ReferenceEquals(str, str1)); Console.WriteLine(str.Equals(str1)); Console.WriteLine(str == str1); Console.WriteLine(object.Equals(str, str1)); } }
運(yùn)行的結(jié)果如下:
第三種方法返回的結(jié)果與修改之前不一致,==運(yùn)算符返回的結(jié)果是false,這是為什么呢?
這是因?yàn)?=運(yùn)算符實(shí)際上是一個(gè)靜態(tài)的方法,對(duì)一非虛方法,在編譯時(shí)就已經(jīng)決定用調(diào)用的是哪一個(gè)方法。在上面的例子中,引用類(lèi)型使用的是ceq指令,而String類(lèi)型調(diào)用是靜態(tài)的op_Equality方法;這兩個(gè)實(shí)例不是同一個(gè)對(duì)象的引用,所以ceq指令執(zhí)行后的結(jié)果是false。
再來(lái)說(shuō)一下==運(yùn)算符與泛型的問(wèn)題,我們創(chuàng)建一個(gè)簡(jiǎn)單的方法,通過(guò)泛型方法判斷兩個(gè)泛型參數(shù)是否相等并在控制臺(tái)上打印出結(jié)果:
static void Equals<T>(T a, T b) { Console.WriteLine(a == b); }
但是當(dāng)我們編譯這段代碼時(shí),VS提示如下錯(cuò)誤:
上面顯示的錯(cuò)誤很簡(jiǎn)單,不能使用==運(yùn)算符比較兩個(gè)泛型T。因?yàn)門(mén)可以是任何類(lèi)型,它可以是引用類(lèi)型、值類(lèi)型,不能提供==運(yùn)算符的具體實(shí)現(xiàn)。
如果像下面這樣修改一下代碼:
static void Equals<T>(T a, T b) where T : class { Console.WriteLine(a == b); }
當(dāng)我們將泛型類(lèi)型T改為引用類(lèi)型,能成功編譯;修改Main方法中的代碼,創(chuàng)建兩個(gè)相同的String類(lèi)型,和以前的例子一樣:
public class Program { static void Main(string[] args) { string str = "Sweet"; string str1 = string.Copy(str); Equals(str, str); } static void Equals<T>(T a, T b) where T : class { Console.WriteLine(a == b); } }
輸出的結(jié)果如下:
結(jié)果與您預(yù)期的結(jié)果不一樣吧,我們期待的結(jié)果是true,輸出的結(jié)果是false。不過(guò)仔細(xì)思考一下,也許會(huì)找到答案,因?yàn)榉盒偷募s束是引用類(lèi)型,==運(yùn)算符對(duì)于引用類(lèi)型使用的是引用相等,IL代碼可以證明這一點(diǎn):
如果我們泛型方法中的==運(yùn)算符改為使用Equals方法,代碼如下:
static void Equals<T>(T a, T b) { Console.WriteLine(object.Equals(a, b)); }
我們改用Equals,也可以去掉class約束;如果我們?cè)俅芜\(yùn)行代碼,控制臺(tái)打印的結(jié)果與我們預(yù)期的一致,這是因?yàn)檎{(diào)用是虛方法object.Equals(object)重寫(xiě)之后的實(shí)現(xiàn)。
但是其它的問(wèn)題來(lái)了,如果對(duì)于值類(lèi)型,這里就會(huì)產(chǎn)生裝箱,有沒(méi)有解決的辦法呢?關(guān)于這一點(diǎn),我們直接給出答案,有時(shí)間專(zhuān)門(mén)來(lái)討論這個(gè)問(wèn)題。
將比較的值類(lèi)型實(shí)現(xiàn)IEquatable<T>接口,并將比較的代碼改為如下,這樣可以避免裝箱:
static void Equals<T>(T a, T b) { Console.WriteLine(EqualityComparer<T>.Default.Equals(a, b)); }
總結(jié)
對(duì)于基元類(lèi)型==運(yùn)算符的底層機(jī)制使用的是ceq指令,通過(guò)CPU寄存器進(jìn)行比較;
對(duì)于引用類(lèi)型==運(yùn)算符,它也使用的ceq指令來(lái)比較內(nèi)存地址;
對(duì)于重載==運(yùn)算符的類(lèi)型,實(shí)際上調(diào)用的是op_equality這個(gè)特殊的方法;
盡量保證==操作符重載和Object.Equals(Object)虛方法的寫(xiě)返回的是相同的結(jié)果;
對(duì)于值類(lèi)型,Equals方法默認(rèn)是通過(guò)反射遍歷所有字段并檢查每個(gè)字段的相等性,為了提高性能,我們需要重寫(xiě)該方法;
值類(lèi)型默認(rèn)情況下不能使用==運(yùn)算符,需要實(shí)現(xiàn)==運(yùn)算符的重載;
以上所述是小編給大家介紹的C#中的==運(yùn)算符,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)我們網(wǎng)站的支持!
上一篇:C#實(shí)現(xiàn)縮放和剪裁圖片的方法示例
欄 目:C#教程
下一篇:C# XML操作類(lèi)分享
本文標(biāo)題:C#中的==運(yùn)算符
本文地址:http://mengdiqiu.com.cn/a1/C_jiaocheng/5632.html
您可能感興趣的文章
- 01-10C#實(shí)現(xiàn)將窗體固定在顯示器的左上角且不能移動(dòng)的方法
- 01-10C#實(shí)現(xiàn)在Form里面內(nèi)嵌dos窗體的方法
- 01-10C#中查找Dictionary中的重復(fù)值的方法
- 01-10C#實(shí)現(xiàn)在啟動(dòng)目錄創(chuàng)建快捷方式的方法
- 01-10C#將圖片存放到SQL SERVER數(shù)據(jù)庫(kù)中的方法
- 01-10關(guān)于nancy中的身份驗(yàn)證
- 01-10C#中的事務(wù)用法實(shí)例分析
- 01-10C#編程自學(xué)之運(yùn)算符和表達(dá)式
- 01-10C#編程自學(xué)之類(lèi)和對(duì)象
- 01-10C#創(chuàng)建不規(guī)則窗體的4種方式詳解


閱讀排行
- 1C語(yǔ)言 while語(yǔ)句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹(shù)的示例代碼(圣誕
- 3利用C語(yǔ)言實(shí)現(xiàn)“百馬百擔(dān)”問(wèn)題方法
- 4C語(yǔ)言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語(yǔ)言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語(yǔ)言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 01-10C#通過(guò)反射獲取當(dāng)前工程中所有窗體并
- 01-10關(guān)于ASP網(wǎng)頁(yè)無(wú)法打開(kāi)的解決方案
- 01-10WinForm限制窗體不能移到屏幕外的方法
- 01-10WinForm繪制圓角的方法
- 01-10C#實(shí)現(xiàn)txt定位指定行完整實(shí)例
- 01-10WinForm實(shí)現(xiàn)仿視頻 器左下角滾動(dòng)新
- 01-10C#停止線程的方法
- 01-10C#實(shí)現(xiàn)清空回收站的方法
- 01-10C#通過(guò)重寫(xiě)Panel改變邊框顏色與寬度的
- 01-10C#實(shí)現(xiàn)讀取注冊(cè)表監(jiān)控當(dāng)前操作系統(tǒng)已
隨機(jī)閱讀
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什
- 01-10delphi制作wav文件的方法
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改
- 01-10使用C語(yǔ)言求解撲克牌的順子及n個(gè)骰子
- 04-02jquery與jsp,用jquery
- 01-11ajax實(shí)現(xiàn)頁(yè)面的局部加載
- 01-10SublimeText編譯C開(kāi)發(fā)環(huán)境設(shè)置
- 01-10C#中split用法實(shí)例總結(jié)
- 01-11Mac OSX 打開(kāi)原生自帶讀寫(xiě)NTFS功能(圖文