今天寫一下C#裏的「==」這個操做符。ui
在剛學C#的時候,我覺得C#裏的==和.NET裏的object.Equals()方法是同樣的,就是一個語法糖而已。其實它們的底層機制是不同的,只不過它們給出的結果在大多數狀況下剛好相同。spa
看個例子:翻譯
這倆方法給出的結果都是True。3d
看起來這兩種方式作了一樣的動做,就是比較兩個值。blog
Build項目,而後使用ildasm看一下生成的il語言(ildasm位置大體在:C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.7.2 Tools)。繼承
使用ildasm打開生成的dll,首先查看Program類裏面的ByEqualMethod方法:接口
能夠看到C#源碼裏調用Equals()的地方直接被翻譯成il語言裏相應的Equals()方法了。。。。內存
而後看一下ByEqualOperator這個方法:字符串
在C#裏該方法使用了==操做符,而在il語言裏,咱們只看到了一個叫作ceq的指令。ceq的意思是compare for equality,就是比較兩個值是否相等,在運行時,它將會被轉換爲硬件上的比較,也許用的是CPU的寄存器。編譯器
針對原始類型,C#的==操做符並無使用.NET裏提供的那些Equals方法,這時==操做符使用專用的彙編語言指令來進行判斷相等性的。
這裏的引用類型不包含string。
看例子,這裏我使用==來比較自定義類MyClass的兩個實例是否相等:
而結果是兩個False:
使用ildasm看一下ByEqualMethod()這個方法:
能夠看到,a.Equals(b)調用的是virtual的object.Equals()方法,參數類型是object,這個應該都能理解。
再看一下ByEqualOperator()方法:
== 操做符翻譯過來仍是使用ceq對兩個參數進行的比較,和以前int類型的例子同樣,除了參數類型不一樣。
因此這應該也是使用CPU的硬件來進行判斷相等性的,那麼像這種引用類型是怎麼經過CPU硬件來比較的呢?由於這兩個類型是引用類型,因此c1,c2兩個變量裏面保存的是它們對應的實例在託管堆中的內存地址,也就是兩個數字而已,因此固然能夠進行比較了。
咱們都知道,==用來判斷string相等性的時候,比較的是string值,而不是引用地址。
看例子:
結果是兩個True:
首先,使用string.Copy()方法能夠保證str1和str2是兩個不一樣的引用。
使用ildasm,先看ByEqualMethod():
能夠看到,這裏a.Equals(b)實際調用的是string實現的IEquatable<T>接口的Equals方法,它的參數是string。
再看一下ByEqualOperator():
此次沒有使用ceq指令,而是調用了一個叫作op_Equality()的方法,這是個什麼方法?
其實它是C#裏 == 操做符的一個重載:static bool op_Equality(string, string)。
在C#裏,當你定義一個類型的時候,你能夠對==操做符進行重載,格式大概以下:
由於il語言裏沒有操做符的概念,而只有方法才能做爲操做符的重載而存在於il裏,因此這裏使用的是靜態方法,它會被翻譯爲一個特殊的靜態方法叫作op_Equality()。
咱們也能夠直接看一下string類的源碼,裏面也是這樣對==進行重載的:
固然,重載了==,也須要重載 !=。
總結一下,使用==來判斷引用類型的相等性,須要按下面的思路順序進行考慮:
1. 該類型是否對 == 進行了重載?若是是,那就是用該重載方法;不然看2
2. 使用ceq指令來比較引用指向的內存地址。
另外還須要再提醒一下的是,string類的==和Equals()方法永遠都會給出同樣的結果。
還有一個原則就是,當你改變某個類型的相等性判斷方法是,要確保==和Equals()方法作的是一樣的事情。
看例子,這裏有兩個值類型:
當我使用==對它們進行比較的時候,直接報錯了。
由於默認狀況下,不可使用==來對非原始類型的值類型進行相等性判斷。要想使用==,就必須提供重載方法。
直接看例子:
針對這兩個tuple,我作了三個相等性判斷,經過第一個ReferenceEquals方法咱們能夠知道這兩個tuple變量指向不一樣的實例。
而tp1.Equals(tp2)返回的是True,這是由於Tuple類(引用類型)重寫了object.Equals()方法,從而比較的是Tuple裏面的值。
儘管微軟爲Tuple把object.Equals()方法重寫了,可是它並無處理==操做符,因此==仍是在比較引用的相等性,因此會返回False。
這樣作確實挺讓人迷惑的。。。
一般狀況下,儘可能使用==操做符,可是有時候==不行,須要使用object.Equals()方法,例如涉及到繼承或者泛型的時候。
直接看例子:
這兩個字符串我作了4個相等性判斷,其結果爲:
不管是object的virtual Equals()方法,仍是==操做符,仍是object的static Equals()方法,都會返回True。
可是我作一下小小的改動:
咱們看看結果會不會變:
結果發生了變化,str1==str2此次返回了False。
這是由於==操做符不是virtual的,它至關因而static的,而static的是沒法virtual的。
如今 str1 == str2 這句話,咱們比較的是兩個類型爲object的變量,儘管咱們知道它們都是string,可是編譯器並不知道。而針對於非virtual的方法或操做符,到底調用哪一個方法是在編譯時決定的,由於這兩個變量的類型是object,因此編譯器會選擇用來比較object的代碼,而object又沒有==操做符的重載,因此==作的就是比較引用的相等性,而這兩個string是不一樣的實例,因此結果會返回False。
因此(object)x == (object)y和ReferenceEquals(x, y)的結果老是同樣的。
針對涉及繼承的相等性判斷,最好仍是使用object.Equals()方法,而不是==操做符。
另外一種不適合使用==操做符的情景是涉及泛型的時候,直接看例子:
這個泛型方法直接報錯了,由於==操做符沒法應用於這兩個操做數T,T能夠是任何類型,例如T是非原始類型的struct,那麼==就不可用。咱們沒法爲泛型指定約束讓其實現某個操做符。針對這個例子,我能夠這樣作,來保證能夠編譯:
如今T是引用類型了,代碼能夠編譯了。咱們使用如下該方法:
按理說這就至關於調用了Equals()方法,結果應該返回True。而實際結果是:
之因此返回了False,是由於泛型方法裏的==操做符比較的是引用,而這又是由於儘管編譯器知道能夠把==操做符應用於類型T,可是它仍然不知道具體是哪一個類型T會重載該操做符,因此它會假設T不會重載==操做符,從而對待這兩個操做數如同object類型同樣並編譯,因此判斷的是引用相等性。
因此泛型方法不會選擇任何的操做符重載,它對待泛型類就像對待object類型同樣。
綜上,針對泛型方法,應該使用Equals()方法,而不是==操做符。