在.Net框架中,如果您查看所有類型的的基類:System.Object類,將找到如下4個與相等判斷的方法:

  除此之外,Microsoft已經(jīng)提供了9個不同的接口,用于比較類型:

   您是否真的理解方法這些方法和接口?如果使用不當(dāng),可能會產(chǎn)生致命的錯誤,并且還會破壞依賴于這些接口的集合。

  接下來我們幾篇博客來討論這些方法和接口,重點關(guān)注的是如何正確使用這些方法和接口。

 

等于的疑惑

  因為存在以下四種原因,會阻礙我們理解相等比較是如何執(zhí)行:

  1. 引用相等與值相等

  2. 判斷值相等的多種方式

  3. 浮點數(shù)的準(zhǔn)確性

  4. 與OOP存在的沖突

 

引用相等與值相等

  眾所周知,在.Net框架中,引用類型在存儲時不包含實際的值,它們包含一個指向內(nèi)存中保存實際值位置的指針,這意味著對于引用類型,有兩種方式來衡量相等性;兩個變量都是指向內(nèi)存中相同的位置,我們稱為引用相等,也可以說是同一個對象;兩個變量指定的位置包括相同的值, 即使它們指向內(nèi)存中不同的位置,我們稱其之為值相等。

   我們可以使用如下示例來說明上述幾點:

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動軟件開發(fā)培訓(xùn),網(wǎng)站設(shè)計培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

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

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動軟件開發(fā)培訓(xùn),網(wǎng)站設(shè)計培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

  我們實例化了兩個Person對象,并且都包含相同的Name屬性;顯然,上述兩個Person類的實例是相同的,它們包含相同的值, 但是運行示例代碼時,控制臺打印輸出的是False,這意味著它們不相等。

   這是因為在.Net框架中,對于引用類型默認(rèn)判斷方式是引用相等,換句話說,"=="運算符會判斷這兩個變量是否指向內(nèi)存中相同的位置,因此在本示例中,盡管Person類的兩個實例包含的值相同,但它們是單獨的實例,變量p1p2兩者分別指內(nèi)存不同的位置。

  引用相等執(zhí)行速度非???,因為只需檢查兩個變量是否指向內(nèi)存中相同的地址,而對于值相等要慢一些。例如,如果Person類不是只有一個字段和屬性,而是具有很多,想檢查Person類的兩個實例是否具有相同的值,您必須檢查每個字段或?qū)傩?。C#中并沒有提供運算符用于檢查兩個類型實例的值是否相等,如果由于某種原因想要實現(xiàn)這種功能,您需要自己編寫代碼來做到這一點。

  現(xiàn)在來看另一個例子:

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動軟件開發(fā)培訓(xùn),網(wǎng)站設(shè)計培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

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

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動軟件開發(fā)培訓(xùn),網(wǎng)站設(shè)計培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

 

   上面的代碼與前一個示例代碼非常相擬,但是在這個示例中,我們使用"=="運算符判斷兩個相同的String類型的變量。我們先給變量s1付值后,然后將變量s1的值復(fù)制并付給另一個變量s2,運行這段代碼,在控制臺打印輸出為True,我們可以說兩個String類型的變量是相等的。

  如果"=="運算符判斷的方式使用的是引用相等, 程序運行時控制臺打印輸出的應(yīng)該是False,但是用于String類型時"==" 運算符判斷方式是值相等。

 

引用相等與值類型

  引用相等和值相等的問題僅適用于引用類型,對于未裝箱的值類型,如整數(shù),浮點型等,變量存儲時已經(jīng)包含了實際的值,這里沒有引用的概念,意味著相等就是比較值。

  以下代碼比較兩個整數(shù),兩者是相等的,因為"=="運算符將比較變量實際的值。

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動軟件開發(fā)培訓(xùn),網(wǎng)站設(shè)計培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

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

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動軟件開發(fā)培訓(xùn),網(wǎng)站設(shè)計培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

  在上面的代碼中,"=="運算符是將變量num1存儲的值與變量num2存儲的值進(jìn)行比較。但是,如果我們修改此代碼并將這兩個變量轉(zhuǎn)換為Object類型,代碼如下:

1 int num1 = 2;2  3 int num2 = 2;4  5 Console.WriteLine((object)num1 == (object)num2);

   運行示例代碼,您看到結(jié)果將是False,與上一次代碼的結(jié)果相反。這是因為Object類型是引用類型,所以當(dāng)我們將整數(shù)轉(zhuǎn)換為Object類型,實際是兩個整數(shù)被裝箱后兩個不同的引用實例,"=="運行符比較的是兩個對象的引用,而不是值。

  好像上面的例子很少見,因為通常情況下我們不會將值類型轉(zhuǎn)換為引用類型,但是存在另一種常見的情況,我們需要將值類型轉(zhuǎn)換為接口。

1 Console.WriteLine((IComparable<int>)num1 == (IComparable<int>)num2);

   為了說明這種情況,我們修改示例代碼,將int類型的變量轉(zhuǎn)換為接口ICompareable<int>;這是.Net框架提供的一個接口,int類型實現(xiàn)這個接口(關(guān)于這個接口我們將其它的博客中討論)。

  在.Net框架中,接口實際上是引用類型,如果我們運行這段代碼,返回的結(jié)果是False。因此,在將值類型轉(zhuǎn)換為接口時,您需要特別小心,如果您進(jìn)行相等檢查,返回的結(jié)果比較的是引用相等。

 

"=="運算符

  如果C#對值類型和引用類型分別提供不同的運算符來判斷相等,也許這些代碼都不是問題,可惜C#只提供一個"=="運算符,也沒有顯示的方式來告訴運算符實際判斷的類型是什么。例如,下面這一行代碼:

1 Console.WriteLine(var1 == var2)

   我們不知道上述的"=="運算符采用的是引用相等還是值相等,因為需要知道"=="運行算判斷的是什么類型,事實上C#也是這樣設(shè)計的。

  在上述內(nèi)容中,我們詳細(xì)介紹了"=="運算符的作用及判斷方式,在閱讀完這篇博客之后,我希望您能比其他開發(fā)者更多的了解當(dāng)使用"=="判斷條件的時候到底發(fā)生了什么,您也能夠更進(jìn)一步了解兩個對象之間的是如何判斷相等的。

 

判斷值相等的多種方式

  復(fù)雜的值相等的還存在另一個問題,通常存在多種方式來比較指定類型的值,String類型是一個最好的例子。 

  經(jīng)常存在這樣一種情況,字符串比較時,可能需要忽略字母的大小寫;例如:在一個電商平臺中搜索一個英文名稱的商品,此時比較商品名稱時,我們需要忽略大小寫,幸運的是在Sql Server數(shù)據(jù)庫中,默認(rèn)使用的是這種比較方式,在.Net框架中有沒有辦法滿足我們的要求?幸運的是在String類型中提供了一個Equals方法的重載,看下面的示例:

1 string s1 = "SWEET";2 3 string s2 = "sweet";4 5 Console.WriteLine(s1.Equals(s2,StringComparison.OrdinalIgnoreCase));

   在程序中運行上面的示例,在控制臺打印輸出的是True

  當(dāng)然.Net框架也提供了多種方式來判斷類型的值相等。最常見方法,類型可以通過實現(xiàn)IEquatable<T>接口定義類型默認(rèn)值相等的判斷方式。如果您不想重新定義自己的類型,.Net框架也提供了其另一種機(jī)制來實現(xiàn)一點,通過實現(xiàn)IEqualityComparer<T>接口來自定義一個比較器,用于判斷同一種類型的兩個實例是否相等。例如:如果您想忽略String類型中的空格進(jìn)行比較,可以自己定義一個比較器,來實現(xiàn)這一功能。
  .Net還提供了一個接口ICompareable<T>,用于判斷當(dāng)前類型大于或小于的比較,也可以通過IComparer<T>接口來實現(xiàn)一個比對器,一般在對象排序時,會用到這些接口。

 

浮點數(shù)的準(zhǔn)確性

  在.Net框架中,您如果使用到浮點數(shù),可以帶來一些意想不到的問題,讓我們來看一個例子:

1 float num1 = 2.000000f;2 float num2 = 2.000001f;3 4 Console.WriteLine(num1 == num2);

  我們有兩個幾乎相等的浮點數(shù),但是很明顯,它們不一樣,因為它們在末尾的數(shù)字是不同的,我們運行程序,控制臺打印輸出的結(jié)果是True。

  從程序來角度來講,它們是相等的,這與我們預(yù)期結(jié)果矛盾。不過您可能已經(jīng)猜測到問題出在哪里了,數(shù)字類型存在一個精度問題,float類型不能存儲足夠的有效數(shù)來區(qū)分這兩個特定的數(shù)字,并且它還存在其它運算的問題??催@個例子:

1 float num1 = 0.7f;2 float num2 = 0.6f + 0.1f;3 4 Console.WriteLine(num2);5 Console.WriteLine(num1 == num2);

   這是一個簡單的計算,我們將0.6與0.1相加,非常明顯,相加后的結(jié)果是0.7,但是我們運行程序,控制臺打印輸出的結(jié)果是False,注意結(jié)果是False,這說明計算結(jié)果不等于0.7。其原因是,浮點數(shù)在運算的過程中出現(xiàn)了舍入誤差導(dǎo)致了存儲一個非常接近的數(shù)字,雖然num2轉(zhuǎn)換成String類型后,在控制臺打印輸出的結(jié)果是0.7,但是num2的值并不等于0.7。

  photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動軟件開發(fā)培訓(xùn),網(wǎng)站設(shè)計培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

  舍入誤差意味著判斷相等通常會給您一個錯誤的結(jié)果,.Net框架沒有提供解決方案。給您的建議是,不要嘗試比較浮點數(shù)是否相等,因為可能不是預(yù)期結(jié)果。這個問題只會影響等于比較,通常不會影響小于和大于比較,在大多數(shù)情況下,比較一個浮點數(shù)是大于還是小于另一個浮點數(shù)不會出該問題。

  在stackoverflow上提供這樣一個解決辦法,供大家參考:https://stackoverflow.com/questions/6598179/the-right-way-to-compare-a-system-double-to-0-a-number-int

 

值相等與面向?qū)ο笾g的矛盾

  這個問題對經(jīng)驗豐富的開發(fā)人員來說可能會感到很詫異,實際上這是等于比較、類型安全和良好的面向?qū)ο髮嵺`之間的沖突。這三個問題如果沒有處理好,將會帶來其它的Bug。

  現(xiàn)在我們來舉這樣一個例子,假設(shè)我們有基類Animal表示動物,派生類Dog來表示狗。

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動軟件開發(fā)培訓(xùn),網(wǎng)站設(shè)計培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

1 public class Animal2 {3 4 }5 6 public class Dog : Animal7 {8 9 }

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動軟件開發(fā)培訓(xùn),網(wǎng)站設(shè)計培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

   如果我們希望在Animal類實現(xiàn)當(dāng)前實例是否等于其它Animal實例,則可能需要實現(xiàn)接口IEquatable<Animal>。這要求它定義一個Equals()方法并以Animal類型的實例作為參數(shù)。

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動軟件開發(fā)培訓(xùn),網(wǎng)站設(shè)計培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

1 public class Animal : IEquatable<animal>2 {3     public virtual bool Equals(Animal other)4     {5         throw new NotImplementedException();6     }7 }

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動軟件開發(fā)培訓(xùn),網(wǎng)站設(shè)計培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

  如果我們希望Dog類也實現(xiàn)當(dāng)前實例是否等于其它Dog實例,那么可能需要實現(xiàn)接口IEquatable<Dog>,這意味著它也定義一個Equals()方法并以Dog類型的實例作為參數(shù)。

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動軟件開發(fā)培訓(xùn),網(wǎng)站設(shè)計培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

1 public class Dog : Animal, IEquatable<Dog>2 {3     public virtual bool Equals(Dog other)4     {5         throw new NotImplementedException();6     }7 }

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動軟件開發(fā)培訓(xùn),網(wǎng)站設(shè)計培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

  現(xiàn)在問題出現(xiàn)了,在這個一個精心設(shè)計的OOP代碼中,您可能會認(rèn)為Dog類會覆蓋Animal類的Equals()方法,但是麻煩的是DogEquals()方法與Animal類的Equals()方法使用的是不同的參數(shù)類型,實際是重寫不了Animal類的Equals()方法。如果您不夠仔細(xì),可能會調(diào)用錯誤的Equals方法,最終返回錯誤的結(jié)果。

  通常的解決辦法是重寫Object類型Equals方法;該方法采用一個Object類型為參數(shù)類型,這意味著它不是類型安全的,但它能夠正常重寫基類的方法,并且這也是最簡單的解決辦法。

 

總結(jié)

  • C#在語法上不區(qū)分值相等和引用相等,這意味著有時候很難預(yù)測在特定情況下"=="運算符是如何執(zhí)行;

  • 存在多種方式實現(xiàn)值相等判斷,.Net框架允許類型定義默認(rèn)的值比較方式,同時提供自己編寫比較器的機(jī)制來實現(xiàn)每種類型的值比較;

  • 不建議使用浮點數(shù)進(jìn)行值相等比較,因為舍入誤差可能導(dǎo)致結(jié)果超出預(yù)期;

  • 值相等、類型安全和良好的面向?qū)ο笾g存在沖突。

 

  轉(zhuǎn)載請注明出自,原文鏈接:http://www.cnblogs.com/tdfblog/p/About-Equality-in-NET.html

http://www.cnblogs.com/tdfblog/p/About-Equality-in-NET.html