<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
我們分別用兩種方式比較兩個整數,第一個使用的是Equals(int)
方法,每二個使用的是==運運算元:
class Program { static void Main(String[] args) { int num1 = 5; int num2 = 5; Console.WriteLine(num1.Equals(num2)); Console.WriteLine(num1 == num2); } }
執行上面的範例,兩個語句出的結果均為true
。我們通過ildasm.exe工具進行反編譯,檢視IL程式碼,瞭解底層是如何執行的。
如果您以前從來沒有接觸過IL指令,不過沒關係,在這裡您不需要理解所有的指令,我們只是想了解這兩個比較方式的差異。
您可以看到這樣一行程式碼:
IL_0008: call instance bool [mscorlib]System.Int32::Equals(int32)
在這裡呼叫的是int
型別Equals(Int32)
方法(該方法是IEquatable<Int>
介面的實現)。
現在再來看看使用==運運算元比較生成的IL指令:
IL_0015: ceq
您可以看到,==執行符使用的是ceq
指令,它是使用CPU暫存器來比較兩個值。C#==運運算元底層機制是使用ceq
指令對基元型別進行比較,而不是呼叫Equals
方法。
修改上面的範例程式碼,將int
型別改為參照型別,編譯後通過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)
程式碼,它是通過呼叫Object.Equals(Object)
虛方法來比較相等,這是在意料之中的事情;現在我們來看==運運算元生成的IL程式碼,與基元型別一致,使用的也是ceq
指令。
接來下來看String
型別的例子:
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)); } }
上面的程式碼與我們以前看過的非常相似,但是這次我們使用String
型別的變數。我們建一個字串,並付給s1
變數,在下一行程式碼我們建立這個字串的副本,並付給另一個變數名稱s2
。
執行上面的程式碼,在控制檯輸出的結果如下:
您可以看到ReferenceEquals
返回false
,這意味著這兩個變數是不同的範例,但是==運運算元和Equals
方法返回的均是true。在String
型別中,==運運算元執行的結果與Equals
執行的結果一樣。
同樣我們使用過ildasm.exe工具反編譯檢視生成IL程式碼。
在這裡我們沒有看到ceq
指令,對String
型別使用==運運算元判斷相等時,呼叫的是一個op_equality(string,string)
的新方法,該方法需要兩個String
型別的引數,那麼它到底是什麼呢?
答案是String
型別提供了==運運算元的過載。在C#中,當我們定義一個型別時,我們可以過載該型別的==運運算元;例如,對於以前的例子中我們實現的Person
類,如果我們為它過載==運運算元,大致的程式碼如下:
public class Person { public string Name { get; set; } public static bool operator ==(Person p1, Person p2) { // 注意這裡不能使用==,否則會導致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); } }
上面的程式碼很簡單,我們實現了==運運算元過載,這是一個靜態方法,但這裡要注意的是,方法的名稱是perator==
,與靜態方法的相似性;事實上,它們會被由編譯器成一個名稱為op_Equality()
的特殊靜態方法。
為了使用事情更加清楚,我們檢視微軟實現的String
型別。
在上面的截圖中,我們可以看到,有兩個運運算元的過載,一個用於相等,另一個是不等式運運算元,其運算方式完全相同,但是否定等於運運算元輸出。需要注意的一點是,如果您想過載一個型別的==執行符的實現,那麼您還需要過載!=操作符的實現,否則編譯會報錯。
在演示值型別的範例前,我們先將Person型別從參照型別改為值型別,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); } }
當我們在嘗試編譯上述程式碼時,VS將提示如下錯誤:
根據錯誤提示,我們需要實現Person結構體的==運運算元過載,過載的語句如下(忽略具體的邏輯):
public static bool operator ==(Person p1, Person p2) { } public static bool operator !=(Person p1, Person p2) { }
當新增上面程式碼後,重新編譯程式,通過ildasm.exe工具反編譯檢視IL程式碼,發現值型別==運運算元呼叫也是op_Equality
方法。
關於值型別,我們還需要說明一個問題,在不重寫Equals(object)
方法時,該方法實現的原理是通過反射遍歷所有欄位並檢查每個欄位的相等性,關於這一點,我們不演示;對於值型別,最好重寫該方法。
我們編寫另一段範例程式碼,宣告兩個String
型別變數,通過4種不同的方式比較運算:
public class Program { public static void Main(string[] args) { string str = "Sweet"; string str1 = string.Copy(str); Console.WriteLine(ReferenceEquals(str, str1)); Console.WriteLine(str.Equals(str1)); Console.WriteLine(str == str1); Console.WriteLine(object.Equals(str, str1)); } }
輸出的結果如下:
首先,我們使用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
型別:
public class Program { public static void Main(string[] args) { object str = "Sweet"; object str1 = string.Copy((string)str); Console.WriteLine(ReferenceEquals(str, str1)); Console.WriteLine(str.Equals(str1)); Console.WriteLine(str == str1); Console.WriteLine(object.Equals(str, str1)); } }
執行的結果如下:
第三種方法返回的結果與修改之前不一致,==運運算元返回的結果是false
,這是為什麼呢?
這是因為==運運算元實際上是一個靜態的方法,對一非虛方法,在編譯時就已經決定用呼叫的是哪一個方法。在上面的例子中,參照型別使用的是ceq
指令,而String
型別呼叫是靜態的op_Equality
方法;這兩個範例不是同一個物件的參照,所以ceq
指令執行後的結果是false
。
再來說一下==運運算元與泛型的問題,我們建立一個簡單的方法,通過泛型方法判斷兩個泛型引數是否相等並在控制檯上列印出結果:
static void Equals<T>(T a, T b) { Console.WriteLine(a == b); }
但是當我們編譯這段程式碼時,VS提示如下錯誤:
上面顯示的錯誤很簡單,不能使用==運運算元比較兩個泛型T。因為T可以是任何型別,它可以是參照型別、值型別,不能提供==運運算元的具體實現。
如果像下面這樣修改一下程式碼:
static void Equals<T>(T a, T b) where T : class { Console.WriteLine(a == b); }
當我們將泛型型別T改為參照型別,能成功編譯;修改Main
方法中的程式碼,建立兩個相同的String
型別,和以前的例子一樣:
public class Program { static void Main(string[] args) { string str = "Sweet"; string str1 = string.Copy(str); Equals(str, str1); } static void Equals<T>(T a, T b) where T : class { Console.WriteLine(a == b); } }
輸出的結果如下:
結果與您預期的結果不一樣吧,我們期待的結果是true
,輸出的結果是false
。不過仔細思考一下,也許會找到答案,因為泛型的約束是參照型別,==運運算元對於參照型別使用的是參照相等,IL程式碼可以證明這一點:
如果我們泛型方法中的==運運算元改為使用Equals
方法,程式碼如下:
static void Equals<T>(T a, T b) { Console.WriteLine(object.Equals(a, b)); }
我們改用Equals
,也可以去掉class
約束;如果我們再次執行程式碼,控制檯列印的結果與我們預期的一致,這是因為呼叫是虛方法object.Equals(object)
重寫之後的實現。
但是其它的問題來了,如果對於值型別,這裡就會產生裝箱,有沒有解決的辦法呢?關於這一點,我們直接給出答案,有時間專門來討論這個問題。
將比較的值型別實現IEquatable<T>
介面,並將比較的程式碼改為如下,這樣可以避免裝箱
static void Equals<T>(T a, T b) { Console.WriteLine(EqualityComparer<T>.Default.Equals(a, b)); }
對於基元型別==運運算元的底層機制使用的是ceq
指令,通過CPU暫存器進行比較;
對於參照型別==運運算元,它也使用的ceq
指令來比較記憶體地址;
對於過載==運運算元的型別,實際上呼叫的是op_equality
這個特殊的方法;
儘量保證==操作符過載和Object.Equals(Object)
虛方法的寫返回的是相同的結果;
對於值型別,Equals
方法預設是通過反射遍歷所有欄位並檢查每個欄位的相等性,為了提高效能,我們需要重寫該方法;
值型別預設情況下不能使用==運運算元,需要實現==運運算元的過載;
由於==運運算元過載實現實際上是一個靜態的方法,在泛型類或方法中使用時與實際的結果可能存在差別,使用Equals
方法可以避免這個問題。
到此這篇關於C#中的==運運算元用法講解的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45