分享

Net编程接口剖析系列之比较和排序 (IComparable和IComparer)

 雄Frank 2011-10-23

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

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多