最稳的pk10计划iphone 北京pk10计划手机软件 北京pk10数字的规律 超神手机版pk10软件 pk10北京赛车9码技巧 pk10四期倍投计划表 pk10极速赛车论坛 北京赛车冠军怎样选5码 北京赛车系统下载安装 pk10教程视频 北京pk10选号公式 北京赛车pk10赚钱技巧 北京赛车怎么提升概率 pk10技巧北京快三 北京pk10大小计划
VB.net 2010 視頻教程 VB.net 2010 視頻教程 VB.net 2010 視頻教程
SQL Server 2008 視頻教程 c#入門經典教程 Visual Basic從門到精通視頻教程
當前位置:
首頁 > 編程開發 > c#教程 >
  • C#教程之C#相等性 - “==”

  • 2019-04-13 19:06 來源:未知

今天寫一下C#里的“==”這個操作符。

原始類型

假象

在剛學C#的時候,我以為C#里的==和.NET里的object.Equals()方法是一樣的,就是一個語法糖而已。其實它們的底層機制是不一樣的,只不過它們給出的結果在大多數情況下恰好相同。

看個例子:

這倆方法給出的結果都是True。

看起來這兩種方式做了同樣的動作,就是比較兩個值。

 

底層原理

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相等性的時候,比較的是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

直接看例子:

結果如下:
 

針對這兩個tuple,我做了三個相等性判斷,通過第一個ReferenceEquals方法我們可以知道這兩個tuple變量指向不同的實例。

而tp1.Equals(tp2)返回的是True,這是因為Tuple類(引用類型)重寫了object.Equals()方法,從而比較的是Tuple里面的值。

盡管微軟為Tuple把object.Equals()方法重寫了,但是它并沒有處理==操作符,所以==還是在比較引用的相等性,所以會返回False。

這樣做確實挺讓人迷惑的。。。

 

比較==和object.Equals()方法

 

通常情況下,盡量使用==操作符,但是有時候==不行,需要使用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()方法,而不是==操作符。

 

博客文章可以隨便轉載和抓取. 
pk10赛车冠军技巧
最稳的pk10计划iphone 北京pk10计划手机软件 北京pk10数字的规律 超神手机版pk10软件 pk10北京赛车9码技巧 pk10四期倍投计划表 pk10极速赛车论坛 北京赛车冠军怎样选5码 北京赛车系统下载安装 pk10教程视频 北京pk10选号公式 北京赛车pk10赚钱技巧 北京赛车怎么提升概率 pk10技巧北京快三 北京pk10大小计划
星秀赚钱吗 快乐12手机板走势图 十七k写小说赚钱吗 2018投资厂房工地能赚钱吗 开网店卖茶叶赚钱吗 36选7中奖奖金多少 捕鱼来了双ss炮台哪个好 恐龙快打无限币典藏版 矿泉水经销商赚不赚钱吗