IComparable接口
System.IComparable接口指定了一种允许一个对象可基于某些特定键值进行排序的行为。
namespace System { [ComVisible(true)] public interface IComparable { int CompareTo(object obj); } }
CompareTo()方法背后的逻辑是,根据某个特定数据字段比较传入的对象与当前实例。CompareTo()方法的返回值被用来判断这个类型小于、大于或是等于它所比较的对象。
- 任何小于0的数字:这个实例在指定对象之前
- 0:这个实例等于指定对象
- 任何大于0的数字:这个实例在指定对象之后
构建可比较对象
System.Array类定义了一个名为 Sort()的静态方法。在内置类型(int、short、string等)上调用这个方法的时候,可以以数字/字母顺序对数组中的项排序,因为这些内置数据类型实现了IComparable。
构建可排序的Car类型
namespace ComparableCar { class Car : IComparable { public int CurrentSpeed { get; set; } public string PetName { get; set; } public int CarID { get; set; }
public Car() { } public Car( string name, int currSp, int id ) { CurrentSpeed = currSp; PetName = name; CarID = id; }
int IComparable.CompareTo(object obj) { Car temp = obj as Car; if (temp != null) { if (this.CarID > temp.CarID) return 1; if (this.CarID < temp.CarID) return -1; else return 0; } else throw new ArgumentException("Parameter is not a Car!"); } } }
由于C#int数据类型(只是CLR System.Int32的简写形式)实现了IComparable,我们就可以按如下所示的方法实现ICompareTo()方法:
int IComparable.CompareTo( object obj ) { Car temp = obj as Car; if (temp != null) return this.CarID.CompareTo(temp.CarID); else throw new ArgumentException("Parameter is not a Car!"); }
Car类型已经知道如何将它自己和类似对象进行对比:
namespace ComparableCar { class Program { static void Main( string[] args ) { Console.WriteLine("***** Fun with Object Sorting ***** ");
Car[] myAutos = new Car[5]; myAutos[0] = new Car("Rusty", 80, 1); myAutos[1] = new Car("Mary", 40, 234); myAutos[2] = new Car("Viper", 40, 34); myAutos[3] = new Car("Mel", 40, 4); myAutos[4] = new Car("Chucky", 40, 5); Console.WriteLine("Here is the unordered set of cars:"); foreach (Car c in myAutos) Console.WriteLine("{0} {1}", c.CarID, c.PetName); Array.Sort(myAutos); Console.WriteLine(); Console.WriteLine("Here is the ordered set of cars:"); foreach (Car c in myAutos) Console.WriteLine("{0} {1}", c.CarID, c.PetName); Console.ReadLine(); } } }
指定多个排序顺序IComparer
如果要构建一个既可通过ID排序又可通过昵称排序的Car类型,就需要与另一个标准接口IComparer打交道。
namespace System.Collections { [ComVisible(true)] public interface IComparer { int Compare(object x, object y); } }
与IComparable接口不同,IComparer接口不是在要排序的类型(即Car)中,而是在许多辅助类中实现的,其中每个排序各有一个依据(如昵称、ID号等)。
namespace ComparableCar { // 这个辅助类用来通过昵称排序Car类型的数组 public class PetNameComparer : IComparer { // 测试每个对象的昵称 int IComparer.Compare( object o1, object o2 ) { Car t1 = o1 as Car; Car t2 = o2 as Car; if (t1 != null && t2 != null) return String.Compare(t1.PetName, t2.PetName); else throw new ArgumentException("Parameter is not a Car!"); } } }
System.Array有许多重载的Sort()方法,其中有一个用来在对象上实现IComparer接口。
namespace ComparableCar { class Program { static void Main( string[] args ) { ... // 按照昵称进行排序 Array.Sort(myAutos, new PetNameComparer()); Console.WriteLine("Ordering by pet name:"); foreach (Car c in myAutos) Console.WriteLine("{0} {1}", c.CarID, c.PetName); ... } } }
自定义属性、自定义排序类型
值得指出的是,在通过特定数据字段排序Car类型的时候,可以使用自定义的静态属性辅助对象用户。假定Car类型添加了一个静态只读属性SortByPetName,它返回一个实现了IComparer接口的对象的实例(在本例中为PetNameComparer):
namespace ComparableCar {
// 现在可以使用一个自定义静态属性来返回正确的IComparer接口 class Car : IComparable {
... // 返回SortByPetName比较的属性 public static IComparer SortByPetName { get { return (IComparer)new PetNameComparer(); } }
... } }
现在可以使用强关联属性按照昵称排序,而不是只能使用独立的PetNameComparer类型:
// 简洁明了的按照昵称排序 Array.Sort(myAutos, Car.SortByPetName);