Effective C# Item26:使用IComarable和IComparer接口实现排序关系
.NET框架定义了两个接口来描述类型的排序关系:IComparable和IComparer,其中IComparable接口定义了类型的自然排序方式,IComparer则为类型提供了另外的排序方式。
我们来看下面的代码。
代码
上面的代码定义了一个结构体,实现了IComparable接口,接口中方法的声明方式是int CompareTo(object obj),该方法接收一个Object对象,和当前对象进行比较,如果当前对象大于比较对象,则返回结果大于0;如果当前对象小于比较对象则返回结果为-1;如果当前对象和比较对象相同,则返回0。
1 public struct Employee : IComparable
2 {
3 private string m_strName;
4 public string Name
5 {
6 get { return m_strName; }
7 set { m_strName = value; }
8 }
9
10 private int m_nAge;
11 public int Age
12 {
13 get { return m_nAge; }
14 set { m_nAge = value; }
15 }
16
17 public Employee(string name, int age)
18 {
19 m_strName = name;
20 m_nAge = age;
21 }
22
23 public int CompareTo(object obj)
24 {
25 if (!(obj is Employee))
26 {
27 throw new ArgumentException("Type Error!");
28 }
29 Employee emp = (Employee)obj;
30 return this.Name.CompareTo(emp.Name);
31 }
32 }
下面是测试代码。
代码
上述代码执行后,会在命令行中输出如下内容:emp1 is larger than emp2.
1 private static void Test()
2 {
3 Employee emp1 = new Employee("Wing", 24);
4 Employee emp2 = new Employee("UnKnown", 25);
5 int nResult = emp1.CompareTo(emp2);
6 if (nResult > 0)
7 {
8 Console.WriteLine("emp1 is larger than emp2.");
9 }
10 else if (nResult < 0)
11 {
12 Console.WriteLine("emp1 is smaller than emp2.");
13 }
14 }
我们再来看上面的代码,其中Employee类型是一个结构体,属于值类型,但是IComparable接口的方法接收参数的类型是Object,这样我们在CompareTo()方法中必须进行装箱和拆箱的操作,才能完成比较操作。这样做对性能的影响比较大,特别是需要比较的数据非常多的时候,例如一个大数据量的集合。为了解决我们需要对上述Employee类型的代码进行修改,修改后的代码如下。
代码
上述代码中,我们显示实现了IComparable接口,但是将访问限制符改为默认设置,即internal,同时添加了一个重载类型的CompareTo()方法, 接收一个Employee类型对象作为参数。这样,在执行Test()方法时,就会调用重载后的方法,绕过了装箱和拆箱。
1 public struct Employee : IComparable
2 {
3 private string m_strName;
4 public string Name
5 {
6 get { return m_strName; }
7 set { m_strName = value; }
8 }
9
10 private int m_nAge;
11 public int Age
12 {
13 get { return m_nAge; }
14 set { m_nAge = value; }
15 }
16
17 public Employee(string name, int age)
18 {
19 m_strName = name;
20 m_nAge = age;
21 }
22
23 int IComparable.CompareTo(object obj)
24 {
25 if (!(obj is Employee))
26 {
27 throw new ArgumentException("Type Error!");
28 }
29 Employee emp = (Employee)obj;
30 return CompareTo(emp);
31 }
32
33 public int CompareTo(Employee emp)
34 {
35 return this.Name.CompareTo(emp.Name);
36 }
37 }
注意:在实现了IComparable接口后,不需要重写Equals()方法,因为大部分情况下,Equals()方法是针对对象的引用进行比较,而IComparable接口是针对对象中的内容进行比较;因此可以出现以下的情况:两个对象调用CompareTo()方法后返回为0,但是调用Equals()方法后返回false。
另外,如果我们实现了IComparable接口,一般情况下需要对操作符进行重载,这些操作符包括:<、>、<=、>=和!=。
如果我们需要自己定制比较规则,那么我们可以通过实现IComparer接口来实现这个目标。
来看下面的代码。
代码
上面的代码中,首先定义了一个实现了IComparer接口的类型,该类型的Compare()方法中,以Employee的Age作为比较的依据。然后定义了一个测试方法,定义了一个元素类型是Employee类型的List,然后以两种方式对List进行排序,并输出排序后的结果。
1 public class AgeComparer : IComparer<Employee>
2 {
3 public int Compare(Employee x, Employee y)
4 {
5 return x.Age.CompareTo(y.Age);
6 }
7 }
8
9
10 //Test Method
11 private static void Test()
12 {
13 List<Employee> listEmp = new List<Employee>();
14 listEmp.Add(new Employee("Wing", 24));
15 listEmp.Add(new Employee("UnKnown", 25));
16 listEmp.Sort();
17 Console.WriteLine("ouput info with default sort:");
18 foreach (Employee emp in listEmp)
19 {
20 Console.WriteLine(emp.Name);
21 }
22
23 Console.WriteLine("output info with specific sort:");
24 AgeComparer comparer = new AgeComparer();
25 listEmp.Sort(comparer);
26 foreach (Employee emp in listEmp)
27 {
28 Console.WriteLine(emp.Name);
29 }
30 }
上面Test()方法的执行结果如下所示。
综上,IComparable接口和IComparer接口为类型实现排序关系提供了两种标准的机制,IComparable接口应该用于为类型实现最自然的排序关系,而ICpmparer接口则用于定制排序的方式。
作者:李潘
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。