天天看點

C# - 實作類型的比較

.NET 裡,IComparable<T>是用來作比較的最常用接口。

如果某個類型的執行個體需要與該類型的其它執行個體進行比較或者排序的話,那麼該類型就可以通過實作IComparable<T>接口來達到此目的。

IComparable<T>隻提供了一個方法:

C# - 實作類型的比較

先看一個例子,這裡使用了string,因為string實作了該接口:

C# - 實作類型的比較

其結果是:

C# - 實作類型的比較

string是通過按位字母進行比較的,“a”就小于“b”,是以上述str1應該是小于str2的。

而CompareTo方法傳回的是int類型,而比較的結果呢,可能有三種情況:

x == y

x < y

x > y

再通過上面的例子,我們可以看出來:

針對x.CompareTo(y),

如果 x == y,那麼 結果 = 0

如果 x < y,那麼結果 < 0

如果 x > y,那麼結果 > 0

我們可以把代碼重構一下,提取出一個低級别方法,便于邏輯複用:

C# - 實作類型的比較
C# - 實作類型的比較

順便提一下,string并沒有實作> < == 等等操作符。

所有的原始類型都實作了IComparable<T>。

是以使用上面的方法,也可以比較原始資料類型:

C# - 實作類型的比較
C# - 實作類型的比較

當然這些類型也可以使用操作符,例如:

C# - 實作類型的比較

而string沒有實作這些操作符,是以這樣寫就是錯誤的:

C# - 實作類型的比較

直接看圖:

C# - 實作類型的比較

其中,針對比較性,System.object并沒有支援,因為對于大多數類型而言,對它們的執行個體進行比較排序是沒有意義的。

例如3 < 4,這樣就是合理的;而送出按鈕 < 取消按鈕,這就沒有意義了;這個委托 < 另一個委托,這也沒有意義。

針對相等性而言,IEquatable<T>僅僅就是對object裡的那些Equals方法的補充。而針對比較性而言,IComparable<T>是主打的方式。

其它的方式都有對應。

下面兩個黃色的通過”插件的方式“實作的,這裡隻提一下,不介紹了。

C# - 實作類型的比較

判斷相等性的時候,可能判斷的是引用相等或者是值相等。

而進行比較排序的時候,其比較的隻能是值,因為對引用進行比較排序是沒有意義的。

而==和!=操作符可以為原始資料類型和引用類型來使用,而>, <, >=, <= 隻能用于原始資料類型。

其實我通常不在我的類型上去實作IComparable<T>,包括引用類型和原始類型。

因為是這樣的,比如說有一個Person(人)這個類型,我想對它排序,按照年齡排序,可以;按照姓名排序,也可以;按照身高排序,也可以;但是沒有任何一種排序對人來說是最理所當然的。

更好的辦法是實作某種比較器。

但是有時候還是需要實作IComparable<T>,那麼下面就講一下怎麼做。

Person Struct:

C# - 實作類型的比較

如果直接使用我們之前的方法,則會報錯:

C# - 實作類型的比較

因為它沒實作IComparable<T>接口。

使用大于号小于号的話,也會報錯:

C# - 實作類型的比較

因為這個類型也沒有實作比較操作符。

C# - 實作類型的比較

很簡單,直接調用了字段Height的CompareTo方法,因為int類型實作了IComparable<T>接口。

一共四個操作符:<, >, <=, >=,必須都得實作。

代碼是:

C# - 實作類型的比較

這個很簡單就不解釋了。

現在代碼不會報錯了:

C# - 實作類型的比較

其運作結果是:

C# - 實作類型的比較

運作OK了,看似沒問題,然後,還有一個問題:

C# - 實作類型的比較

使用等号判斷相等性的代碼會報錯。

如果你不是用==操作符的話,那麼代碼是沒問題的,也是可以進行比較的,也沒人強制要求實作==和!=操作符。但是這很奇怪!因為你說 p1 > p2,這個成立,然後再說 p1 != p2這個就編譯錯誤,那就不合理了。

是以,如果你實作了比較操作符,那麼相等性操作符也應該一同實作了:

C# - 實作類型的比較

那麼既然==和!=都實作了,那麼其它的相等性判斷方法也應該一同實作:

object.Equals()

object.GetHashCode()

IEquatable<T>

看起來挺麻煩,但這隻是一個struct,還是相對簡單的。。。。

但針對struct,其實還沒完,還有一個非泛型的IComparable接口,泛型出現之前,一直都是用這個接口的。

這個接口現在來說沒什麼用了,但是如果有其它遺留的老代碼需要使用你這個struct,你可能還需要把這個接口實作一下。。。😂

引用類型除了需要考慮上面struct考慮的那些東西外,還需要考慮更多的東西。

首先,需要在CompareTo裡面檢查是否為null,和類型檢查。

而如果Person是一個沒有seal的class,那問題就更大了,以前文章裡提到的OOP繼承問題、類型安全問題、相等性問題将全部出現。因為類型安全和比較性還是沒法一起很愉快的工作。反正會很混亂。。。

是以如果事seal的class,那麼在其上實作比較性的話還勉強可以接受;否則的話,祝好運。。。

之前在相等性的文章裡,提到過,針對泛型代碼來說,==和!=操作符不能很好的工作,而object.Equals()卻可以。

這點在比較性裡面也是一樣的。針對泛型的比較,你需要使用IComparable<T>.CompareTo()方法,而不是比較的操作符>, <, >=, <=等(即使實作了比較操作符)。

如果我把之前的方法代碼改成使用比較操作符:

C# - 實作類型的比較

那麼就會報錯,因為無法限制泛型實作了某些操作符。。。但可以考慮在接口裡面實作比較操作符。。。

但是實作比較性的話:

實作IComparable<T>接口

也可選去實作比較操作符。

C# - 實作類型的比較