與C#定義了相等性比較規范一樣,C#也定義了排序比較規范,以確定一個對象與另一個對象的先后順序。排序規范如下
- IComparable接口(包括IComparable接口和IComparable<T>接口)
- >和<運算符
當需要實現排序算法時,使用IComparable接口。在下面的例子中,Array.Sort靜態方法可以調用,是因為System.String類實現了IComparable接口。
string[] colors={"Green", "Red", "Blue"}; Array.Sort(colors) foreach(string c in colors) Console.Write(c+ " ");
而<和>運算符比較特殊,因為他們一般用於比較數字類型。因為大於和小於運算符會被靜態地解析,因此它們“產生”出高效的代碼,適用於復雜計算的場景。
.NET Framework還提供了插件式的排序協議--IComparer接口。IComparable接口與IComparer接口的差別類似與IEquatable和IEqualityComparer接口 (關於IEqutable接口和IEqualityComparer接口,請參考C#相等性:http://www.cnblogs.com/yang_sy/p/3582946.html)
1. IComparable接口
IComparable接口的定義如下
public interface IComparable int CompareTo(Object obj); } public interface IComparable<in T> { int CompareTo(T other); }
這兩個接口定義了相同的功能。對於值類型,IComparable<T>接口效率高於ICompare接口。上面的兩個接口的CompareTo方法都按照下面的方式運行:
- 如果a排在b后面,那么a.CompareTo(b)返回1
- 如果a和不一樣,那么返回0
- 如果a排在不前面,那么返回-1
我們來看下面的示例代碼:
IList<Staff> staffs = new List<Staff> { new Staff{FirstName="AAA", Title="Manager", Dept="Sale"}, new Staff{FirstName="BBB", Title="Accountant", Dept="Finance"}, new Staff{FirstName="CCC", Title="Accountant", Dept="Finance"}, }; Console.WriteLine("BBB".CompareTo(staffs[0].FirstName)); // 1 Console.WriteLine("BBB".CompareTo(staffs[1].FirstName)); // 0 Console.WriteLine("BBB".CompareTo(staffs[2].FirstName)); // -1
C#的大部分基本類型都實現了IComparable接口和IComparable<T>接口。很多自定義類型同樣也實現了該接口,這樣便於排序。
IComarable與Equals
假設一個類型重寫了Equals方法並實現了IComparable接口。那么你肯定希望當Equals返回true時,CompareTo應當返回0。而Equals返回false時,CompareTo可以返回任何值。
換句話說,相等性比對比性更嚴格;反之則不會。因此,當CompareTo說“兩個對象相等”時,Equals會說“這兩個對象不一定相等”。一個很好的例子來自System.String類。String.Equals方法和==運算符使用序號排序規則比較字符串--也就是通過每個字符的Unicode的值進行排序。而String.CompareTo方法,卻使用不那么嚴格的基於文化區域(culture-dependent)進行比較。對於大多數計算機,字符ǖ和ṻ,Equals返回False,而CompareTo返回0
你可以實現通過IComparer接口,從而完成特定的排序算法。自定義IComparer接口的實現,進一步加大了CompareTo和Equals方法之間的差異。比如不區分大小寫的字符串比較器,對於A和a,將返回0. 這也從反面印證了,ComparTo方法不如Equals方法嚴格。
2. <和>運算符
一些類型,定義了<和>運算符,比如:
bool after2010 = DateTime.Now > new DateTime(2010, 1, 1); Console.WriteLine(after2010);
當實現<和>運算符之后,你需要保證<和>運算符與IComparable接口保持一致。這也是.NET Framework的標准。
同樣地,當一個類型重載了<和>運算符,那么也要求實現IComparable接口,而反之則不需要。實際上,大多數.NET類型實現了IComparable接口,並沒有重載<和>運算符。這(排序比較)與相等性比較不一樣:
- 在實現相等性比較時,如果重載了Equals方法,那么一般都重載==運算符
- 而在實現排序性比較時,如果實現了CompareTo方法,一般不要求重載<運算符和>運算符
一般地,只有在下面的情形中,才需要重載<運算符和>運算符:
- 一個類型本身包含大於和小於這樣的概念
- 執行先后順序比較的方式是唯一的
- 結果不會隨文化區域(Cultures)變化而變化
System.Stirng類型不滿足最后一條,因此string不支持>操作和<操作。因此 “beck” > “Anne”,編譯時會拋出錯誤。
3. 實現IComparable接口
下面的實例代碼中,結構Note表示一個音樂的注釋,它實現了IComparable接口,還重載了<運算符和>運算符。為了實例的完整性,我們還重寫了Equals和GetHashCode方法,以及重載了==和!=運算符,通過這個例子,你可以全面的了解排序比較。
internal struct Note : IComparable, IComparable<Note>, IEquatable<Note> { private int semitonesFromA; public int SemitonesFromA { get { return semitonesFromA; } } public Note(int semitonesFromA) { this.semitonesFromA = semitonesFromA; } // generic IComparable<T> public int CompareTo(Note other) { if (Equals(other)) return 0; return SemitonesFromA.CompareTo(other.SemitonesFromA); } // non-generic IComaparable public int IComparable.CompareTo(object other) { if (!(other is Note)) throw new InvalidOperationException("CompareTo: Not a note"); return CompareTo((Note)other); } public static bool operator <(Note n1, Note n2) { return n1.CompareTo(n2) < 0; } public static bool operator >(Note n1, Note n2) { return n1.CompareTo(n2) > 0; } // for IEquatable public bool Equals(Note other) { return this.SemitonesFromA == other.SemitonesFromA; } // override Object.Equals public override bool Equals(object other) { if (!(other is Note)) throw new InvalidOperationException("CompareTo: Not a note"); return Equals((Note)other); } public override int GetHashCode() { return SemitonesFromA.GetHashCode(); } public static bool operator ==(Note n1, Note n2) { return n1.Equals(n2); } public static bool operator !=(Note n1, Note n2) { return !(n1 == n2); } }