技术开发 频道

.Net编程接口剖析系列之比较和排序


【IT168 专稿】  我们知道,与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 += "\t" + 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\tTotal\tChinese\tEnglish\tMath");

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上学到的,应当不仅仅是比较和排序,而更加应该学到一种思路,一种设计模式,这才是最重要的;另外,它们还有助于加深我们对接口的理解。

0
相关文章