C#之旅(一): 泛型 和IComparable、IComparer
最近在学习C#的一些基础知识,希望以笔记的形式记录下自己在学习当中的所得并与大家分享自己的学习心得,由于还是个初学者还请多多给点意见,大家共同交流共同进步。我也是通过一些比较好的博客边看边学,这里推荐下自我感觉不错的几篇文章,
看后觉对的让我们这些初学者有所得。
先从泛型说起
泛型,从大专时候老师就常常说它的重要性,它的地位是如何的高,但没用过即使再好的东西也是无法体会的,唯有一试,方知其厉害。首先学习一门东西,起码要知道它有什么好处,有什么作用,有哪些用武之地,这样才值得我们花大把的时间在它上面。MSDN给出的答案是:“提高的代码可重用性和类型安全性”。
排序是我们编程中会经常碰到的一个问题,排序所使用的算法也有很多,比如冒泡排序,快速排序等等排序算法。比如我现在使用冒泡排序来实现一个很简单的排序功能,对一个int类型的数组进行一个简单的排序
2
3 {
4
5 int length = array.Length;
6
7 int temp;
8
9
10
11 for (int i = 0; i < length; i++)
12
13 {
14
15 for (int j = 0; j < length - i - 1; j++)
16
17 {
18
19 if (array[j] > array[j + 1])
20
21 {
22
23 temp = array[j];
24
25 array[j] = array[j + 1];
26
27 array[j + 1] = temp;
28
29 }
30
31 }
32
33 }
34
35 }
在main函数里调用
2 {
3 int[] array = { 5,4,3,2,1};
4 Console.WriteLine("排序前:");
5 for (int i = 0; i < array.Length; i++)
6 {
7 Console.Write("\t"+array[i]);
8 }
9 BubbleSort(array);
10 Console.WriteLine("\n\r排序后:");
11 for (int i = 0; i < array.Length; i++)
12 {
13 Console.Write("\t" + array[i]);
14 }
15 }
运行结果:
排序前:
5 4 3 2 1
排序后:
1 2 3 4 5
很好的完成了任务,排序功能实现了,现在又碰到这么种情况不单单要对int类型数组排序,还要对double类型的数组排序,很简单只要将上面的代码Copy一份,在做稍微的改变就好:
2 {
3 int length = array.Length;
4 double temp;
5
6 for (int i = 0; i < length; i++)
7 {
8 for (int j = 0; j < length - i - 1; j++)
9 {
10 if (array[j] > array[j + 1])
11 {
12 temp = array[j];
13 array[j] = array[j + 1];
14 array[j + 1] = temp;
15 }
16 }
17 }
18 }
调用:
2
3 Console.WriteLine("排序前:");
4
5 for (int i = 0; i < array.Length; i++)
6
7 {
8
9 Console.Write("\t"+array[i]);
10
11 }
12
13 BubbleSort(array);
14
15 Console.WriteLine("\n\r排序后:");
16
17 for (int i = 0; i < array.Length; i++)
18
19 {
20
21 Console.Write("\t" + array[i]);
22
23 }
输出结果:
排序前:
5.1 4.1 3.1 2.1 1.1
排序后:
1.1 2.1 3.1 4.1 5.1
任务完成了,但隐隐感觉这种实现有点问题,感觉不是很完美。现在以这种Copy的方式来达到代码重用显然不是我们想要的。我忽然想到OOD原则的DIP(Dependence Inversion Principle)依赖倒置原则:具体细节应该依赖抽象,抽象不应该依赖具体细节,要依赖于抽象,不依赖于具体。冒泡排序是否就是一个抽象呢,而具体对哪种类型进行排序这里的类型是否就是具体呢!我们现在就是违背了这条原则才会导致路越走越窄。
时代造英雄,这时候泛型也就应运而生。它很好的解决了代码的复用性,将具体类型从排序算法中抽离出来,实现它们二者之间的解耦。
我很快就写出了如下代码:
2 {
3 int length = array.Length;
4 T temp;
5
6 for (int i = 0; i < length; i++)
7 {
8 for (int j = 0; j < length - i - 1; j++)
9 {
10 if (array[j]> array[j + 1])
11 {
12 temp = array[j];
13 array[j] = array[j + 1];
14 array[j + 1] = temp;
15 }
16 }
17 }
18 }
这时方法体的声明有点变化,它将类型也当成参数(会发现这里将类型参数取名为T,这不是必须的,可以取任意名称,只是大家习惯使用T来命名),这样就不会依赖具体的某种类型了,按理来说任何类型的排序都适用了,不管以后在碰到其它类型的排序如byte,都可以很好的解决。调用的形式也变成了BubbleSort<int>(array)这种形式,通过方法名后面尖括号还传递具体的某种类型。但还有一个问题会发现就是上面的泛型版的代码根本就连编译都不能通过,问题就出在
对T类型进行比较操作是不允许的,因为T是未知的一种类型,T有可能实现了比较运算符也有可能没有。
再说IComparable
这个时候就要轮到我们的另外两大主角出场了,首先是IComparable,它的声明如下:
2
3 {
4
5 int CompareTo(Object obj)
6
7 }
MSDN上的解释:定义通用的比较方法,由值类型或类实现以创建类型特定的比较方法。
也就是说我们的值类型实现了这个借口,而int,double这些基本的数据类型属于值类型,那它们就应该实现了这个接口,我们可以看下它们的声明:
2
3 public struct Double : IComparable, IFormattable, IConvertible, IComparable<double>, IEquatable<double>
重新改写下我们的泛型版的实现:
2
3 {
4
5 int length = array.Length;
6
7 T temp;
8
9
10
11 for (int i = 0; i < length; i++)
12
13 {
14
15 for (int j = 0; j < length - i - 1; j++)
16
17 {
18
19 if (array[j].CompareTo(array[j + 1]) == 1)
20
21 {
22
23 temp = array[j];
24
25 array[j] = array[j + 1];
26
27 array[j + 1] = temp;
28
29 }
30
31 }
32
33 }
34
35 }
只要给它加where约束是它必须继承IComrapable接口,在比较时只要调用CompareTo()方法,这时同样还是T类型,也就是一个未知类型,但我们前面已经添加了一个约束必须实现CompareTo()方法,所以这里它可以调用CompareTo()方法进行比较。
int类型的就这样调用
double类型的就这样调用
这样就很好的解决了代码复用,不同的类型复用相同的排序算法,这是我们想要的。而且该算法还可以用于我们自定义的类型,比如我有一个Person类声明如下:
2 {
3 public string name;
4 public int age;
5 public Person(string name, int age)
6 {
7 this.name = name;
8 this.age = age;
9 }
10 public int CompareTo(object obj)
11 {
12 Person other = (Person)obj;
13 return this.age.CompareTo(other.age);
14 }
15 }
Person它实现了IComparable接口,并在CompareTo方法中返回年龄比较的结果。这样就可以对Person对象实现按年龄来进行排序。
比如我实例化一些对象
2
3 new Person("吴朝剑", 21),
4
5 new Person("叶华斌", 23),
6
7 new Person("王昌文",22)};
输出结果:
姓名:吴朝剑,年龄21
姓名:王昌文,年龄22
姓名:叶华斌,年龄23
姓名:朱 ,年龄25
三大主角上场了两位,现在该是我们最后一大主角出场的时候了,有情我们的IComparer隆重登场(掌声欢迎!!)。
(⊙v⊙)嗯,没有掌声,可能大家都对它还比较陌生所以不鼓掌,也不知道它有什么厉害的。好我们就先说它的作用,及它的使用场合。
最后说IComparer
还拿上面的Person类说事,假如现在客户提出一种需求要求我们对姓名实现排序,那我们改怎么办(这时IComparer看着IComparable暗自发笑)。IComparable怯怯的说那对我的CompareTo方法进行更改成按姓名排序不就好了。这时场下的嘉宾OCP(开放-封闭-原则)发怒了:你这个愚蠢的家伙,你这样做不仅违背了我的意愿同时你还没能解决掉问题的根源,要是下次再要求你按年龄排序怎么办。IComparer听罢更加得意了。好吧现在我先介绍下IComparer的背景吧:
2 {
3 int Compare(Object x,Object y);
4 }
MSDN上的解释:公开一种比较两个对象的方法。
看下我们使用了IComparer的解决方案:
2 {
3 public int Compare(Person x, Person y)
4 {
5 return x.name.CompareTo(y.name);
6 }
7 }
8 public static void BubbleSort<T>(T[] array, IComparer comparer)
9 {
10 int length = array.Length;
11 T temp;
12
13 for (int i = 0; i < length; i++)
14 {
15 for (int j = 0; j < length - i - 1; j++)
16 {
17 if (comparer.Compare(array[j], array[j + 1]) == 1)
18 {
19 temp = array[j];
20 array[j] = array[j + 1];
21 array[j + 1] = temp;
22 }
23 }
24 }
25 }
我们新建类SortPersonByName来实现对Person按姓名排序。
调用方式:
2 new Person("吴朝剑", 21),
3 new Person("叶华斌", 23),
4 new Person("王昌文",22)};
5 BubbleSort<Person>(personArray,new SortPersonByName());
6 //Array.Sort<Person>(personArray, new SortPersonByName());
7 for (int i = 0; i < personArray.Length; i++)
8 {
9 Console.WriteLine(string.Format("姓名:{0},年龄{1}\n\r"
10 , personArray[i].name, personArray[i].age));
11 }
结果:
姓名:王昌文,年龄22
姓名:吴朝剑,年龄21
姓名:叶华斌,年龄23
姓名:朱 ,年龄25
我们看到可以使用Array自带的Sort排序算法来对我们的Person类进行排序:
//Array.Sort<Person>(personArray, new SortPersonByName());
但必须要实现它的泛型版本IComparer接口如下:
2 {
3 public int Compare(Person x, Person y)
4 {
5 return x.name.CompareTo(y.name);
6 }
7 }
总结
何时使用IComprable何时使用IComparer接口有时候是使自己迷惑的地方,我也上网看了些资料,但没找到比较好的例子,英文资料倒是找着一篇,但由于本人英语较菜所以理解也困难,只讲代码看了个大概,现在自己总结下:
1. IComparable接口就如它的定义一样,它定义了一种通用性较强的比较方法,是那种放之四海而皆准的,所以使用继承的方式使类中实现比较的方法跟类本身耦合在一起。就比如基本数据类型的比较大小。
2. IComparer则是对类的比较方式进行扩展,这种扩展不是通过继承的方式,而是添加一个新的比较规则(类)来对现有对象的比较进行扩展,就像在比如一个Student类他有学号,性别等字段,可以分别实现按学号或是性别来进行排序。
好了很迟了,洗洗睡吧!!(*^__^*) 嘻嘻……