遇到问题应该多思考一下——由一个泛型方法想到的

今天在群里,有一个同学发上来了一段代码,说是从书上看到的例子,但是编译不了(有些书的作者真是误人子弟),希望帮忙找一下错在哪里,该怎么改,代码如下:

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比较是合法的。如果泛型类型参数没有约束为引用类型,则对同一个泛型类型的两个变量用==进行比较是非法的,

posted @ 2012-11-28 22:47 Lukexywang 阅读(...) 评论(...) 编辑 收藏