今天在群里,有一個同學發上來了一段代碼,說是從書上看到的例子,但是編譯不了(有些書的作者真是誤人子弟),希望幫忙找一下錯在哪里,該怎么改,代碼如下:
public class SortHelper { public void BubbleSort<T>(T[] array) { if (array==null) { throw new ArgumentNullException("array"); } T temp = default(T); for (int i = 0; i < array.Length; i++) { for (int j = i+1 ; j < array.Length; j++) {if (array[i]>= array[j])
{ temp = array[i]; array[i] = array[j]; array[j] = temp; } } } } }
很明顯,他是想要寫一個泛型的冒泡排序方法,可是這段代碼是編譯通不過的,編譯器報的錯誤是“ Operator '>=' cannot be applied to operands of type 'T' and 'T' ”。看來問題出在代碼中標紅的if的判斷表達式那里,這里涉及到了兩個泛型變量的比較問題。看來使用>=來對兩個T類型的變量進行比較是行不通的,那么在排序方法里該怎么比較兩個元素的大小呢?
在遇到問題的時候,我們應該自己先思考一下問題出在哪里,該怎么解決,如果毫無頭緒的話,再尋求幫助,不要想直接得到答案,通過思考,能有更深刻的理解。
對於這個泛型排序方法,我們來思考一下,我們想要對傳入的T[]數組進行排序,但是不要忽略了排序的前提條件,就是數組T[]中的元素必須是可以比較大小的,如果數組T[]中的元素根本無法比較大小,排序也就成為了空中樓閣。所以,這里對於類型參數T,我們應該加以限制,讓其實可以與同類型對象進行比較,要對T加上這個限制該怎么做呢?自然是使用類約束,我想大多數人都想到了.net基類庫中為我們提供的IComparable<T>接口,關於這個接口,MSDN里說的很清楚,這里不再贅述。於是上面的代碼被進行了小小的改造,如下:
public class SortHelper { public static void BubbleSort<T>(T[] array) where T:IComparable<T> { if (array==null) { throw new ArgumentNullException("array"); } T temp = default(T); for (int i = 0; i < array.Length; i++) { for (int j = i+1 ; j < array.Length; j++) {if (array[i].CompareTo(array[j])>=0 )
{ temp = array[i]; array[i] = array[j]; array[j] = temp; } } } } }
我們給泛型的類型參數加上了約束,約束其必須實現IComparable<T>接口,這樣在方法內部,我們就可以調用CompareTo(T)方法來對兩個元素進行比較,我們使用如下代碼測試一下
class Program { static void Main(string[] args) {
//int,double,float,string,byte等基元類型都是實現了IComparable接口的 int[] arrayInt=new int[]{4,2,8,12,6,90,1}; string[] arrayStr=new string[]{"abc","abcd","123","12","milk","hello"}; SortHelper.BubbleSort<int>(arrayInt); SortHelper.BubbleSort<string>(arrayStr); OutPut(arrayInt); Console.WriteLine(); OutPut(arrayStr); } static void OutPut<T>(T[] array) { foreach (T element in array) { Console.Write(element); Console.Write(" "); } } }
程序運行輸出如下
看起來好像很完美,但是實際上這種方式存在2個問題:
1、由於約束了T必須實現接口IComparable<T>,所以我們無法為沒有實現該接口的對象進行排序,這一點使這個泛型排序方法的適用范圍大大縮小了,而我們寫泛型方法的初衷就是算法的重用,希望使其對盡可能多的類型都可以使用這個方法來進行排序。
2、由於要求類型T實現IComparable<T>接口,該接口中的方法也只能被實現一次,但是我們在對一個類型進行排序的時候,可能會按照種規則來進行排序,比如對象有A和B兩個屬性,有時候希望按照A屬性的排序規則來排序,有時候希望按照B屬性的排序規則來排序,這時候上面的泛型排序方法就顯得不夠靈活了.
我們得繼續思考了,如何能夠解決這兩個問題呢?如何能夠讓沒有實現IComparable接口的對象也可以使用這個方法進行排序,讓這個方法成為一個萬能方法,並且可以適用於不同的排序規則。要對一個類型的多個對象進行排序,上面說過了,要保證這個類型的兩個對象之間是可以比較大小的,其實這個泛型排序方法真正需要的並不要求T的兩個變量之間可以比較大小,排序方法需要獲得的實際上是比較大小的規則。使用泛型類型約束的手段,實際上是把比較大小的規則集成到了要比較的對象中,這一點造成了這二個問題,所以解決這二個問題的關鍵就是把比較大小的規則從對象中移除,單獨傳入到排序方法中,我想大家應該都想到該怎么做了,就是給泛型方法BubbleSort<T>增加一個參數,用於接收比較大小的規則。在這里小小驕傲一下,如果使用java的話,這個參數的類型將不得不是一個接口,如果是接口的話,我們將不得不寫一個類來實現這個接口,封裝比較大小規則的方法,好麻煩呀。好在微軟為我們提供了委托,不僅提供了委托,后來又有了lambda表達式,說實話,我真是愛死委托了。所以終級改造版的代碼是這個樣子的:
public class SortHelper { //參數param1GreaterThanParam2用於判斷第一個參數是不是比第二個參數大 public static void BubbleSort<T>(T[] array,Func<T,T,bool> param1GreaterThanParam2) { if (array==null) { throw new ArgumentNullException("array"); } T temp = default(T); for (int i = 0; i < array.Length; i++) { for (int j = i+1 ; j < array.Length; j++) { if (param1GreaterThanParam2(array[i],array[j])) { temp = array[i]; array[i] = array[j]; array[j] = temp; } } } } }
測試代碼如下,為了測試,新增了一個類Person,假設Person類存在於第三方提供的一個dll中,有Name(string類型)和Age(int類型)兩個屬性
class Program { static void Main(string[] args) { int[] arrayInt=new int[]{4,2,8,12,6,90,1}; string[] arrayStr=new string[]{"abc","abcd","123","12","milk","hello"}; SortHelper.BubbleSort<int>(arrayInt, (x, y) => { return x > y; }); SortHelper.BubbleSort<string>(arrayStr, (x, y) => { return x.CompareTo(y)>0; }); Console.WriteLine("對int[]數組進行排序"); OutPut(arrayInt); Console.WriteLine(); Console.WriteLine("對string[]數組進行排序"); OutPut(arrayStr); Console.WriteLine(); Person[] persons=new Person[] { new Person(){Age=10,Name = "Tom"}, new Person(){Age = 6,Name = "Jerry"}, new Person(){Age = 15,Name = "Lucy"}, new Person() {Age = 21,Name = "Lana"}, new Person(){Age=1,Name="Baby"} }; SortHelper.BubbleSort<Person>(persons, (x, y) => { return x.Age > y.Age; }); Console.WriteLine("對數組persons按照每個人的年齡排序"); OutPut(persons,true); SortHelper.BubbleSort<Person>(persons,(x,y)=> { return x.Name.CompareTo(y.Name)>0; }); Console.WriteLine("對數組persons按照每個人的名字排序"); OutPut(persons,true); } static void OutPut<T>(T[] array,bool newLineForEach=false) { foreach (T element in array) { Console.Write(element); if (newLineForEach) { Console.WriteLine(); } else { Console.Write(" "); } } } }
測試結果如圖
一個通用的泛型排序方法完成。
最后提一下泛型方法中泛型變量之間進行比較的幾個准則。
如果對於泛型類型參數T未加約束,則T可以使用==操作符與null進行比較,因為如果T是值類型,則與null比較永遠返回false,如果T是引用類型,與null比較是合法的。如果泛型類型參數沒有約束為引用類型,則對同一個泛型類型的兩個變量用==進行比較是非法的,


