| 我们知道,与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 { Total =0, Chinese, English, Math, } public class Scores //分数类,用于存储分数 { int[] _score = new int[4]; public int this[SubjectEnum score] { get { return _score[(int)score]; } set { _score[(int)score] = value; } } public override string ToString() { string str = ""; foreach (int score in _score) { str += " " + score.ToString(); } return str; } } public class Student:IComparable //学生类 { string _name; public string Name { get { return _name; } set { _name = value; } } Scores _scores=new Scores(); public Scores Scores { get { return _scores; } set { _scores = value; } } public Student(string name,int chinese, int english, int math) { _name = name; _scores[SubjectEnum.Chinese] = chinese; _scores[SubjectEnum.English] = english; _scores[SubjectEnum.Math] = math; _scores[SubjectEnum.Total] = chinese +english +math; } public override string ToString() { return _name + _scores.ToString(); } #region IComparable Members public int CompareTo(object obj) { if (!(obj is Student)) throw new ArgumentException("Argument not a Student", "obj"); return Name.CompareTo(((Student)obj).Name); } #endregion }
来看看我们的Main函数,我们在一个数组中存储了若干个学生,并且利用了Array.Sort对起进行了排序。
static void Main(string[] args) { Student[] students = new Student[4]; students[0] = new Student("Michale", 80, 90, 70); students[1] = new Student("Jack", 90, 80, 75); students[2] = new Student("Alex", 88, 85, 95); students[3] = new Student("Rose", 92, 91, 65); Array.Sort(students); Console.WriteLine("Name Total Chinese English Math"); foreach (Student student in students) { Console.WriteLine(student); } Console.ReadKey(); }
下面来看看输出结果:
| 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> { SubjectEnum _subject; bool _reverse; public StudentComparer(SubjectEnum subject, bool reverse) { _subject = subject; _reverse = reverse; } #region IComparer<Student> Members public int Compare(Student left, Student right) { if (left == null && right == null) return 0; else if (left == null) return -1; else if (right == null) return 1; //比较响应科目的成绩 int result = left.Scores[_subject].CompareTo(right.Scores[_subject]); //如果反序,只要将结果取反即可 if (_reverse) result = -result; return result; } #endregion }
一个功能强大的比较器就这样实现了,那么接下来我们就来实现将学生按照总分进行从高到底的排序,这里我们只需要对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上学到的,应当不仅仅是比较和排序,而更加应该学到一种思路,一种设计模式,这才是最重要的;另外,它们还有助于加深我们对接口的理解。 |