我们知道,与C++相比较,C#以及整个.Net并不支持多继承,而相应的,C#支持了接口,并且支持一个类型实现多个接口。对于接口的概念,相信大部分读者已经有了很好的了解,而我这里谈谈个人对于接口理解,只求抛砖引玉。
在我认为,一个接口就是一个对类型的某种能力的认证,并且是以某种标准化的形式将这种能力规范出来。你的类型实现了某个接口,换而言之,也就是说这个类型具备了此接口所标识的能力。比如现在出国留学考托福GRE,开车考驾照这些东西,其实就是相当于我们编程中接口;从某种意义上说,你通过了GRE,就说明你具备在国外学习所需要的语言能力,而你考取了驾照,就证明了你具有上路行驶的能力了。接口同样如此,给你类型实现特定的一些接口,就是给他们标记了他们所具备的特别能力,而一些依赖这些能力的功能,得以用通用的代码实现重用,实现可扩展。
我的这个关于接口的系列文章,主要是对.Net编程一些非常重要的接口来进行详细讲解,深入了解这些接口的原理和应用。这对于我们写出精简优美的代码,是非常有帮助的;毕竟,我们在知道自己想做什么之后,首先应该知道.Net Framework能给我们做什么。
在本篇以及后续的几篇文章我们将会谈到以下几个主题:
(一)比较和排序(IComparable和IComparer)
(二)枚举(IEnumerable和IEnumerator)
(三) 序列化(ISerializable和IXmlSerializable)
System.IComparable & System.IComparable<T>
顾名思义,一个实现了IComparable的class应该就是一个可以对实例进行相互比较的class,我们先来看看它的定义:
以下为引用的内容: [ComVisible(true)] public interface IComparable { int CompareTo(object obj); } |
这个接口相当简单,只提供了一个接口函数:CompareTo,如果当前对象比被比较的对象小,那么返回负数;如果相当,则返回0;如果当前对象比被比较的对象大,则返回正数。
但是,如果你觉得这个接口仅仅是能够让你比较两个对象大小,那么你就错了,这个接口更大的作用是能够实现了该类型线性数据结构的排序功能。比如List<T>.Sort()和Array的静态方法Sort都能够很好地利用IComparable来对数据进行排序,排序算法由类库实现,对于我们来说,只需要让自己的类型实现IComparable接口,负责比较两个对象大小的算法就可以了。
IComparable<T>是一个泛型接口,用于实现对特定类型的对象的比较,用法和IComparable基本一致,这里不再进行赘述,下面的例子也是根据IComparable来写的。
我们来看看下面的代码,这里定义了一个学生类Student,每个学生有自己名字和分数。Student类实现了IComparable接口,两个学生之间直接按照名字进行比较。顺便说明Scores类用于存储学生的成绩。
以下为引用的内容: public enum SubjectEnum
来看看我们的Main函数,我们在一个数组中存储了若干个学生,并且利用了Array.Sort对起进行了排序。
|
下面来看看输出结果:
Name | Total | Chinese | English | Math |
Alex | 268 | 88 | 85 | 95 |
Jack | 245 | 90 | 80 | 75 |
Michale | 240 | 80 | 90 | 70 |
Rose | 248 | 92 | 91 | 65 |
以下为引用的内容: System.Collections.IComparer & System.Collections.Generic. IComparer<T> |
IComparer是这么样的一个接口,它是用于实现一个专门的“比较器”,这个比较器可以对传入的两个对象比较大小。我们来看看它的定义:
以下为引用的内容: [ComVisible(true)] public interface IComparer { int Compare(object x, object y); } |
大家可能会对IComparer存在的必要性有点疑问,那就是既然我们有了IComparable就能够实现对象的比较以及排序,那么还需要IComparer做什么呢,岂不是画蛇添足?我的回答是:不,IComparer的存在很有必要,因为它可以用来实现一些专门的和功能更加强大的比较器。就如现代社会的分工一样,以前落后的小农经济一去不复返了,社会上的各成员要进行相互协作才能发挥最高的效率;同样,我们设立专业的IComparer,使得比较的功能得以扩展和专业化,你有了更多的选择。将对象进行比较的时候,你可以使用不同的IComparer来使用不同的方法来比较,就像我们购买商品选择不同的品牌一样(试想这件东西不是购买的而是你自己生产的话,那么你就失去了选择的机会了)。另外专门的IComparer也可以提供一些属性,来让我们的比较变得更加灵活。
光说太抽象,我们下面还是继续上一节对学生进行排序的问题进行讨论。这里我们可以创建一个专门的学生比较类StudentComparer, 而它则实现了IComparer的泛型接口System.Collections.Generic.IComparer<Student>,StudentComparer的作用是根据成绩对学生进行比较。为了将IComparer的优越性体现出来,我们这里在StudentComparer的构造函数中增加了两个参数subject和reverse,前者用于指定我们要按照何种科目成绩进行比较,而后者则指定是否将结果取反(当然我们也可以使用Array.Reverse方法来将结果按照降序排列,这里只是实现方法之一)。好,这样我们比较器就这样设计好了,看看下面的代码:
以下为引用的内容: public class StudentComparer: System.Collections.Generic.IComparer<Student> |
一个功能强大的比较器就这样实现了,那么接下来我们就来实现将学生按照总分进行从高到底的排序,这里我们只需要对main函数进行稍微的修改就可以了,使用Array.Sort的另外一个重载方法Array.Sort (T[], Generic IComparer) 来进行比较。
看到上面我们在StudentComparer的构造函数中传入了Total(总分)和True(降序),我们看看执行结果:
Name | Total | Chinese | English | Math |
Alex | 268 | 88 | 85 | 95 |
Rose | 248 | 92 | 91 | 65 |
Jack | 245 | 90 | 80 | 75 |
Michale | 240 | 80 | 90 | 70 |
太棒了,IComparer是这样的神奇,想象一下如果没有IComparer而仅仅要用IComparable来实现上面的功能,将是多么麻烦的事情,更加重要的是,那会将Student类的代码变的一团糟,就如同一个上班族却天天要想着回家给自己种的蔬菜浇浇水,给自己养的猪喂喂食一样,这些琐碎的东西会让你的生活一团糟的。
.Net的库类的排序功能是如此强大,以至于我们还能够利用代理来进行排序(其实是将比较功能写在自己的专门函数中),但是本文的重点是讲解接口,所以这里对利用代理排序不再详述,只是提一下而已。
我们从IComparable和IComparer上学到的,应当不仅仅是比较和排序,而更加应该学到一种思路,一种设计模式,这才是最重要的;另外,它们还有助于加深我们对接口的理解。