遇到問題應該多思考一下——由一個泛型方法想到的


今天在群里,有一個同學發上來了一段代碼,說是從書上看到的例子,但是編譯不了(有些書的作者真是誤人子弟),希望幫忙找一下錯在哪里,該怎么改,代碼如下:

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(" ");
            }
        }
    }

程序運行輸出如下

image

看起來好像很完美,但是實際上這種方式存在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(" ");
                }
            }
        }
    }

測試結果如圖

image

一個通用的泛型排序方法完成。

最后提一下泛型方法中泛型變量之間進行比較的幾個准則。

如果對於泛型類型參數T未加約束,則T可以使用==操作符與null進行比較,因為如果T是值類型,則與null比較永遠返回false,如果T是引用類型,與null比較是合法的。如果泛型類型參數沒有約束為引用類型,則對同一個泛型類型的兩個變量用==進行比較是非法的,


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM