值比较

  考虑两个表示人的Person对象,它们都有一个Age整型属性。下面要比较它们,看看哪个人年龄较大。为此可以使用以下代码:

  1. if(person1.Age > person2.Age)
  2. {
  3. ...
  4. }

  这是可以的,但还有其他方法,例如,使用下面的语法:

  1. if(person1 > person2)
  2. {
  3. ...
  4. }

  可以使用运算符重载,如本节后面所述。这是一项强大的技术,但应谨慎使用。在上面的代码中,年龄的比较不是非常明显,该段代码还可以比较身高、体重、IQ等。

  另一个方法是使用IComparable和IComparer接口,它们可以采用标准的方式定义比较对象的过程。.NET Framework中的各种集合类支持这种方式,这使得它们成为对集合中的对象进行排序的一种极佳方式。

  1. 运算符重载

  通过运算符重载(operator overloading),可以对我们设计的类使用标准的运算符,例如+>等。这称为重载,因为在使用特定的参数类型时,我们为这些运算符提供了自己的实现代码,其方式与重载方法相同,也是为同名方法提供不同的参数。

  运算符重载非常有用,因为我们可以在运算符重载的实现中执行需要的任何操作,这并不一定像用“+”表示“把这两个操作数相加”这么简单。稍后介绍一个进一步升级CardLib库的示例。我们将提供比较运算符的实现代码,比较两张牌,看看在一圈(扑克牌游戏中的一局)中哪张牌会赢。

  因为在许多扑克牌游戏中,一圈取决于牌的花色,这并不像比较牌上的数字那样直接。如果第二张牌与第一张牌的花色不同,则无论其点数是什么,第一张牌都会赢。考虑两个操作数的顺序,就可以实现这种比较。也可以考虑“王牌”的花色,而王牌可以胜过其他花色,即使该王牌的花色与第一张牌不同,也是如此。也就是说,card1 > card2 是true(这表示如果card1是第一个出牌,则card1胜过了card2),并不意味着card2 > card1是false。如果card1和card2都不是王牌,且属于不同的花色,则这两个比较都是true。

  但我们先看一下运算符重载的基本语法。要重载运算符,可给类添加运算符类型成员(它们必须是static)。一些运算符有多种用途(如 - 运算符就有一元和二元两种功能),因此我们还指定了要处理多少个操作数,以及这些操作数的类型。一般情况下,操作数的类型与定义运算符的类相同,但也可以定义处理混合类型的运算符,详见稍后的内容。

  例如,考虑一个简单类型AddClass1,如下所示:

  1. public class AddClass1
  2. {
  3. public int val;
  4. }

  这仅是int值的一个包装器(wrapper),但可以用于说明原理。对于这个类,下面的代码不能编译:

  1. AddClass1 op1 = new AddClass1();
  2. op1.val = 5;
  3. AddClass1 op2 = new AddClass1();
  4. op2.val = 5;
  5. AddClass1 op3 = op1 + op2;

  其错误是+运算符不能应用于AddClass1类型的操作数,因为我们尚未定义要执行的操作。下面的代码则可执行,但无法得到预期的结果:

  1. AddClass1 op1 = new AddClass1();
  2. op1.val = 5;
  3. AddClass1 op2 = new AddClass1();
  4. op2.val = 5;
  5. bool op3 = op1 == op2;

  其中,使用==二元运算符来比较op1和op2,看它们是否引用同一个对象,而不是验证它们的值是否相等。在上述代码中,即使op1.val和op2.val相等,op3也是false。

  要重载+运算符,可使用下述代码:

  1. public class AddClass1
  2. {
  3. public int val;
  4. public static AddClass1 operator + (AddClass1 op1, AddClass1 op2)
  5. {
  6. AddClass1 returnVal = new AddClass1();
  7. returnVal.val = op1.val + op2.val;
  8. return returnVal;
  9. }
  10. }

  可以看出,运算符重载看起来与标准静态方法声明类似,但它们使用关键字operator和运算符本身,而不是一个方法名。现在可以成功地使用+运算符和这个类,如上面的示例所示:

  1. AddClass1 op3 = op1 + op2;

  重载所有的二元运算符都是一样的,一元运算符看起来也是类似的,但只有一个参数:

  1. public class AddClass1
  2. {
  3. public int val;
  4. public static AddClass1 operator +(AddClass1 op1, AddClass1 op2)
  5. {
  6. AddClass1 returnVal = new AddClass1();
  7. returnVal.val = op1.val + op2.val;
  8. return returnVal;
  9. }
  10. public static AddClass1 operator -(AddClass1 op1)
  11. {
  12. AddClass1 returnVal = new AddClass1();
  13. returnVal.val = -op1.val;
  14. return returnVal;
  15. }
  16. }

  这两个运算符处理的操作数的类型与类相同,返回值也是该类型,但考虑下面的类定义:

  1. public class AddClass1
  2. {
  3. public int val;
  4. public static AddClass3 operator + (AddClass1 op1, AddClass2 op2)
  5. {
  6. AddClass3 returnVal = new AddClass3();
  7. returnVal.val = op1.val + op2.val;
  8. return returnVal;
  9. }
  10. public class AddClass2
  11. {
  12. public int val;
  13. }
  14. public class AddClass3
  15. {
  16. public int val;
  17. }
  18. }

  下面的代码就可以执行:

  1. AddClass1 op1 = new AddClass1();
  2. op1.val = 5;
  3. AddClass2 op2 = new AddClass2();
  4. op2.val = 5;
  5. AddClass3 op3 = op1 + op2;

  可以酌情采用这种方式混合类型。但要注意,如果把相同的运算符添加到AddClass2中,上面的代码就会失败,因为它弄不清要使用哪个运算符。因此,应注意不要把签名相同的运算符添加到多个类中。

  还要注意,如果混合了类型,操作数的顺序必须与运算符重载的参数顺序相同。如果使用了重载的运算符和顺序错误的操作数,操作就会失败。所以不能像下面这样使用运算符:

  1. AddClass3 op3 = op2 + op1;

  当然,除非提供了另一个重载运算符和倒序的参数:

  1. public static AddClass3 operator + (AddClass2 op1, AddClass1 op2)
  2. {
  3. AddClass3 returnVal = new AddClass3();
  4. returnVal.val = op1.val + op2.val;
  5. return returnVal;
  6. }

  可以重载下述运算符:

• 一元运算符:+-!~++truefalse• 二元运算符:+-*/%&|^<<>>• 比较运算符:==!=<><=>=

如果重载true和false运算符,就可以在布尔表达式中使用类,例如,if(op1){}。

  不能重载赋值运算符,例如+=,但这些运算符使用与它们对应的简单运算符,例如+,所以不必担心它们。重载+意味着+=如期执行。=运算符不能重载,因为它有一个基本的用途。但这个运算符与用户定义的转换运算符相关,详见下一节。

  也不能重载&&||,但它们使用对应的运算符&|执行计算,所以重载&|就足够了。

  一些运算符(如<>)必须成对重载。这就是说,如重载>,就不能重载<。许多情况下,可以在这些运算符中调用其他运算符,以减少需要的代码数量(和可能发生的错误),例如:

  1. public class AddClass1
  2. {
  3. public int val;
  4. public static bool operator >= (AddClass1 op1, AddClass1 op2)
  5. {
  6. return (op1.val >= op2.val);
  7. }
  8. public static bool operator < (AddClass1 op1, AddClass1 op2)
  9. {
  10. return !(op1 >= op2);
  11. }
  12. // Also need implementations for <= and > operators.
  13. }

  在较复杂的运算符定义中,这可以减少代码行数。这页意味着,如果后来决定修改这些运算符的实现,需要改动的代码将较少。

  这同样适用于==!=,但对于这些运算符,通常需要重写Object.Equals()和Object.GetHashCode(),因为这两个函数也可以用于比较对象。重写这些方法,可以确保无论类的用户使用什么技术,都能得到相同的结果。这不太重要,但应增加进来,以保证其完整性。它需要下述非静态重写方法:

  1. public class AddClass1
  2. {
  3. public int val;
  4. public static bool operator == (AddClass1 op1, AddClass1 op2)
  5. {
  6. return (op1.val == op2.val);
  7. }
  8. public static bool operator != (AddClass1 op1, AddClass1 op2)
  9. {
  10. return !(op1 == op2);
  11. }
  12. public override bool Equals(object op1)
  13. {
  14. return val == ((AddClass1)op1).val;
  15. }
  16. public override int GetHashCode()
  17. {
  18. return val;
  19. }
  20. }

  GetHashCode()可根据其状态,获取对象实例的一个唯一int值。这里使用val就可以了,因为它也是一个int值。

  注意,Equals()使用object类型参数。我们需要使用这个签名,否则就将重载这个方法,而不是重写它。类的用户仍可以访问默认的实现代码。这样就必须使用数据类型转换得到所需的结果。这常常需要使用本章前面讨论的is运算符检查对象类型,代码如下所示:

  1. public override bool Equals(object op1)
  2. {
  3. if(op1 is AddClass1)
  4. {
  5. return val == ((AddClass1)op1).val;
  6. }
  7. else
  8. {
  9. throw new ArgumentException("Cannot compare AddClass1 objects with object of type" + op1.GetType().ToString());
  10. }
  11. }

  在这段代码中,如果传送给Equals的操作数的类型有误,或者不能转换为正确类型,就会抛出一个异常。当然,这可能并不是我们希望的操作。我们要比较一个类型的对象和另一个类型的对象,此时需要更多的分支结构。另外,可能只允许对类型完全相同的两个对象进行比较,这需要对第一个if语句做如下修改:

  1. if(op1.GetType() == typeof(AddClass1))

  2. 给CardLib添加运算符重载

  现在再次升级Ch11CardLib项目,给Card类添加运算符重载。在本章下载代码的Ch11CardLib文件夹中可以找到以下的类的代码。首先给Card类添加额外字段,指定某花色比其他花色大,使A有更高的级别。把这些字段指定为静态,因为设置它们后,它们就可以应用到所有Card对象上:

  1. public class Card
  2. {
  3. /// <summary>
  4. /// Flag for trump usage. If true, trumps are valued higher
  5. /// than cards of other suits.
  6. /// </summary>
  7. public static bool useTrumps = false;
  8. /// <summary>
  9. /// Trump suit to use if useTrumps is true.
  10. /// </summary>
  11. public static Suit trump = Suit.Club;
  12. /// <summary>
  13. /// Flag that determines whether aces are higher than kings or lower
  14. /// than deuces.
  15. /// </summary>
  16. public static bool isAceHigh = true;
  17. }

  这些规则应用于应用程序中每个Deck的所有Card对象上。因此,两个Deck中的Card不可能遵守不同规则。这适用于这个类库,但是确实可以做出这样的假设:如果一个应用程序要使用不同的规则,可以自行维护这些规则;例如,在切换牌时,设置Card的静态成员。

  完成后,就要给Deck类再添加几个构造函数,以便用不同的特性来初始化扑克牌:

  1. /// <summary>
  2. /// Nondefault constructor. Allows aces to be set high.
  3. /// </summary>
  4. public Deck(bool isAceHigh) : this()
  5. {
  6. Card.isAceHigh = isAceHigh;
  7. }
  8. /// <summary>
  9. /// Nondefault constructor. Allows a trump suit to be used.
  10. /// </summary>
  11. public Deck(bool useTrumps, Suit trump) : this()
  12. {
  13. Card.useTrumps = useTrumps;
  14. Card.trump = trump;
  15. }
  16. /// <summary>
  17. /// Nondefault constructor. Allows aces to be set high and a trump suit
  18. /// to be used.
  19. /// </summary>
  20. public Deck(bool isAceHigh, bool useTrumps, Suit trump) : this()
  21. {
  22. Card.isAceHigh = isAceHigh;
  23. Card.useTrumps = useTrumps;
  24. Card.trump = trump;
  25. }

  每个构造函数都使用第9章介绍的:this()语法来定义,这样,无论如何,默认构造函数总会在非默认的构造函数之前调用,初始化扑克牌。

  接着,给Card类添加运算符重载(和推荐的重写代码):

  1. public static bool operator == (Card card1, Card card2)
  2. {
  3. return (card1.suit == card2.suit) && (card1.rank == card2.rank);
  4. }
  5. public static bool operator != (Card card1, Card card2)
  6. {
  7. return !(card1 == card2);
  8. }
  9. public override bool Equals(object card)
  10. {
  11. return this == (Card)card;
  12. }
  13. public override int GetHashCode()
  14. {
  15. return 13 * (int)suit + (int)rank;
  16. }
  17. public static bool operator > (Card card1, Card card2)
  18. {
  19. if(card1.suit == card2.suit)
  20. {
  21. if(isAceHigh)
  22. {
  23. if(card1.rank == Rank.Ace)
  24. {
  25. if(card2.rank == Rank.Ace)
  26. return false;
  27. else
  28. return true;
  29. }
  30. else
  31. {
  32. if(card2.rank == Rank.Ace)
  33. return false;
  34. else
  35. return (card1.rank > card2.rank);
  36. }
  37. }
  38. else
  39. {
  40. return (card1.rank > card2.rank);
  41. }
  42. }
  43. else
  44. {
  45. if(useTrumps && (card2.suit == Card.trump))
  46. return false;
  47. else
  48. return true;
  49. }
  50. }
  51. public static bool operator < (Card card1, Card card2)
  52. {
  53. return !(card1 >= card2);
  54. }
  55. public static bool operator >= (Card car1, Card card2)
  56. {
  57. if(card1.suit == card2.suit)
  58. {
  59. if(isAceHigh)
  60. {
  61. if(card1.rank == Rank.Ace)
  62. {
  63. return true;
  64. }
  65. else
  66. {
  67. if(card2.rank == Rank.Ace)
  68. return false;
  69. else
  70. return (card1.rank >= card2.rank);
  71. }
  72. }
  73. else
  74. {
  75. return (card1.rank >= card2.rank);
  76. }
  77. }
  78. else
  79. {
  80. if(useTrumps && (card2.suit == Card.trump))
  81. return false;
  82. else
  83. return true;
  84. }
  85. }
  86. public static boo operator <= (Card card1, Card card2)
  87. {
  88. return !(card1 > card2);
  89. }

  这段代码没什么需要特别关注之处,只是>>=重载运算符的代码比较长。如果单步执行>运算符的代码,就可以看到它的执行情况,明白为什么需要这些步骤。

  比较两张牌card1和card2,其中card1假定为先出的牌。如前所述,在使用王牌时,这是很重要的,因为王牌胜过其他牌,即使非王牌比较大,也是这样。当然,如果两张牌的花色相同,则王牌是否也是该花色就不重要了,所以这是我们要进行的第一个比较:

  1. public static bool operator > (Card card1, Card card2)
  2. {
  3. if(card1.suit == card2.suit)
  4. {
  5. }

  如果静态的isAceHigh标记为true,就不能直接通过Rank枚举中的值比较牌的点数了。因为A的点数在这个枚举中是1,比其他牌都小。此时就需要如下步骤:

• 如果第一张牌是A,就检查第二张牌是否也是A。如果是,则第一张牌就胜不过第二张牌。 如果第二张牌不是A,则第一张牌胜出:

  1. if(isAceHigh)
  2. {
  3. if(card1.rank == Rank.Ace)
  4. {
  5. if(card2.rank == Rank.Ace)
  6. return false;
  7. else
  8. return true;
  9. }

• 如果第一张牌不是A,也需要检查第二张牌是不是A。如果是,则第二张牌胜出:否则,就可以比较牌的点数,因为此时已不比较A了:

  1. else
  2. {
  3. if(card2.rank == Rank.Ace)
  4. return false;
  5. else
  6. return (card1.rank > card2.rank);
  7. }
  8. }

• 另外,如果A不是最大的,就只需比较牌的点数:

  1. else
  2. {
  3. return (card1.rank > card2.rank);
  4. }

  代码的其余部分主要考虑的是card1和card2花色不同的情况。其中静态useTrumps标记是非常重要的。如果这个标记是true,且card2是王牌,则可以肯定,card1不是王牌(因为这两张牌有不同的花色),王牌总是胜出,所以card2比较大:

  1. else
  2. {
  3. if(useTrumps && (card2.suit == Card.trump))
  4. return false;

  如果card2不是王牌(或者useTrumps是false),则card1胜出,因为它是最先出的牌:

  1. else
  2. return true;
  3. }
  4. }

  另有一个运算符(>=)使用与此类似的代码,除此之外的其他运算符都非常简单,所以不需要详细地分析它们。

  下面的简单客户代码测试这些运算符。把它放在客户项目的Main()函数中进行测试,就像前面CardLib示例的客户代码那样(这段代码包含在Ch11CardClient\Program.cs文件中):

  1. Card.isAceHigh = true;
  2. Console.WriteLine("Aces are high.");
  3. Card.useTrumps = true;
  4. Card.trump = Suit.Club;
  5. Console.WriteLine("Clubs are trumps.");
  6. Card card1, card2, card3, card4, card5;
  7. card1 = new Card(Suit.Club, Rank.Five);
  8. card2 = new Card(Suit.Club, Rank.Five);
  9. card3 = new Card(Suit.Club, Rank.Ace);
  10. card4 = new Card(Suit.Heart, Rank.Ten);
  11. card5 = new Card(Suit.Diamond, Rank.Ace);
  12. Console.WriteLine("{0} == {1} {2}",
  13. card1.ToString(), card2.ToString(), card1 == card2);
  14. Console.WriteLine("{0} == {1} {2}",
  15. card1.ToString(), card3.ToString(), card1 != card3);
  16. Console.WriteLine("{0}.Equals({1}) {2}",
  17. card1.ToString(), card4.ToString(), card1.Equals(card4));
  18. Console.WriteLine("Card.Equals({0}, {1}) {2}",
  19. card3.ToString(), card4.ToString(), Card.Equals(card3, card4));
  20. Console.WriteLine("{0} > {1} {2}",
  21. card1.ToString(), card2.ToString(), card1 > card2);
  22. Console.WriteLine("{0} <= {1} {2}",
  23. card1.ToString(), card3.ToString(), card1 <= card3);
  24. Console.WriteLine("{0} > {1} {2}",
  25. card1.ToString(), card4.ToString(), card1 > card4);
  26. Console.WriteLine("{0} > {1} {2}",
  27. card1.ToString(), card4.ToString(), card1 > card4);
  28. Console.WriteLine("{0} > {1} {2}",
  29. card4.ToString(), card1.ToString(), card4 > card1);
  30. Console.WriteLine("{0} > {1} {2}",
  31. card5.ToString(), card4.ToString(), card5 > card4);
  32. Console.WriteLine("{0} > {1} {2}",
  33. card4.ToString(), card5.ToString(), card4 > card5);
  34. Console.ReadKey();

  在这两种情况下,在应用运算符时都考虑了指定的规则。这在输出结果的最后4行中尤其明显,说明王牌总是胜过其他牌。

  3. IComparable 和 IComparer 接口

  IComparableIComparer 接口是 .NET Framework 中比较对象的标准方式。这两个接口之间的差别如下:

  1. IComparable 在要比较的对象的类中实现,可以比较该对象和另一个对象。
  2. IComparer 在一个单独的类中实现,可以比较任意两个对象。

  一般使用 IComparable 提供了一个方法 CompareTo(),这个方法接受一个对象。例如,在实现该方法时,使其可以接受一个 Person 对象,以便确定这个人比当前的人更年老还是更年轻。实际上,这个方法返回一个int,所以也可以确定第二个人与当前的人的年龄差:

  1. if(person1.CompareTo(person2) == 0)
  2. {
  3. Console.WriteLine("Same age");
  4. }
  5. else if(person1.CompareTo(person2) > 0)
  6. {
  7. Console.WriteLine("person 1 is Older");
  8. }
  9. else
  10. {
  11. Console.WriteLine("person 1 is Younger");
  12. }

  IComparer 也提供了一个方法 Compare()。这个方法接受两个对象,返回一个整型结果,这与 CompareTo() 相同。对于支持 IComparer 的对象,可使用下面的代码:

  1. if(personComparer.Compare(person1, person2) == 0)
  2. {
  3. Console.WriteLine("Same age");
  4. }
  5. else if(personComparer.Compare(person1, person2) > 0)
  6. {
  7. Console.WriteLine("person 1 is Older");
  8. }
  9. else
  10. {
  11. Console.WriteLine("person 1 is Younger");
  12. }

  这两种情况下,提供给方法的参数是 System.Object 类型。这意味着可以比较一个对象与其他任意类型的另一个对象。所以,在返回结果之前,通常需要进行某种类型比较,如果使用了错误类型,还会抛出异常。

  .NET Framework 在类 Comparer 上提供了 IComparer 接口的默认实现方式,类 Comparer 位于 System.Collections 名称空间中,可以对简单类型以及支持 IComparable 接口的任意类型进行特定文化的比较。例如,可通过下面的代码使用它:

  1. string firstString = "First String";
  2. string secondString = "Second String";
  3. Console.WriteLine("Comparing '{0}' and '{1}', result : {2}",
  4. firstString, secondString,
  5. Comparer.Default.Compare(firstString, secondString));
  6. int firstNumber = 35;
  7. int secondNumber = 23;
  8. Console.WriteLine("Comparing '{0}' and '{1}', result : {2}",
  9. firstNumber, secondNumber,
  10. Comparer.Default.Compare(firstNumber, secondNumber));

  这里使用 Comparer.Default 静态成员获取 Comparer 类的一个实例,接着使用 Compare()方法比较前两个字符串,之后比较两个整数,结果如下:

  1. Comparing 'First String' and 'Second String', result : -1
  2. Comparing '35' and '23', result : 1

  在字母表中,F 在 S 的前面,所以 F “小于” S,第一个比较的结果就是 -1。同样,35大于23,所以结果是1。注意这里的结果并未给出相差的幅度。

  在使用 Comparer 时,必须使用可以比较的类型。例如,试图比较 firstString 和 firstNumber 就会生成一个异常。

  下面列出有关这个类的一些注意事项:

  1. 检查传送给 Comparer.Compare()的对象,看看它们是否支持IComparable。如果支持,就使用该实现代码。
  2. 允许使用null值,它表示“小于”其他任意对象。
  3. 字符串根据当前文化来处理。要根据不同的文化(或语言)处理字符串,Comparer类必须使用其构造函数进行实例化,以便传送用于指定所使用的文化的 System.Globalization.CultureInfo 对象。
  4. 字符串在处理时要区分大小写。如果要以不区分大小写的方式来处理它们,就需要使用 CaseInsensitiveComparer 类,该类以相同的方式工作。

  4. 对集合排序

  许多集合类可以用对象的默认比较方式进行排序,或者用定制方法来排序。ArrayList 就是一个示例,它包含方法 Sort(),这个方法使用时可以不带参数,此时使用默认的比较方式,也可以给它传送 IComparer 接口,以比较对象时。

  在给 ArrayList 填充了简单类型时,例如整数或字符串,就会进行默认的比较。对于自己的类,必须在类定义中实现 IComparable,或创建一个支持 IComparer 的类,来进行比较。

  注意,System.Collentions 名称空间中的一些类,包括 CollectionBase,都没有提供排序方法。如果要对派生于这个类的集合排序,就必须多做一些工作,自己给内部的 List 集合排序。

  下面的示例说明如何使用默认的和非默认的比较方式给列表排序。

添加一个新类 Person,修改 Person.cs 中的代码,如下所示:

  1. namespace Ch11Ex05
  2. {
  3. public class Person : IComprable
  4. {
  5. public string Name;
  6. public int Age;
  7. public Person(string name, int age)
  8. {
  9. Name = name;
  10. Age = age;
  11. }
  12. public int CompareTo(object obj)
  13. {
  14. if(obj is Person)
  15. {
  16. Person otherPerson = obj as Person;
  17. return this.Age - otherPerson.Age;
  18. }
  19. else
  20. {
  21. throw new ArgumentException("Object to compare to is not a Person object.");
  22. }
  23. }
  24. }
  25. }

添加一个新类 PersonComparerName,修改代码,如下所示:

  1. using System;
  2. using System.Collections;
  3. using System.Collentions.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. namespace Ch11Ex05
  8. {
  9. public class PersonComparerName : IComperer
  10. {
  11. public static IComparer Default = new PersonComparerName();
  12. public int Compare(object x, object y)
  13. {
  14. if(x is Person && y is Person)
  15. {
  16. return Comparer.Default.Compare(
  17. ((Person)x).Name, ((Person)y).Name);
  18. }
  19. else
  20. {
  21. throw new ArgumentException("One or both objects to compare are not Person objects.");
  22. }
  23. }
  24. }
  25. }

修改 Program.cs 中的代码,如下所示:

  1. using System;
  2. using System.Collections;
  3. using System.Collentions.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. namespace Ch11Ex05
  8. {
  9. class Program
  10. {
  11. static void Main(string[] args)
  12. {
  13. ArrayList list = new ArrayList();
  14. list.Add(new Person("Jim", 30));
  15. list.Add(new Person("Bob", 25));
  16. list.Add(new Person("Bert", 27));
  17. list.Add(new Person("Ernie", 22));
  18. Console.WriteLine("Unsorted people");
  19. for(int i = 0; i < list.Count; i++)
  20. {
  21. Console.WriteLine("{0} ({1})",
  22. (list[i] as Person).Name, (list[i] as Person).Age);
  23. }
  24. Console.WriteLine();
  25. Console.WriteLine(
  26. "People sorted with default comparer (by age):");
  27. list.Sort();
  28. for(int i = 0; i < list.Count; i++)
  29. {
  30. Console.WriteLine("{0} ({1})",
  31. (list[i] as Person).Name, (list[i] as Person).Age);
  32. }
  33. Console.WriteLine();
  34. Console.WriteLine(
  35. "People sorted with nondefault comparer (by name):");
  36. list.Sort(PersonCompareName.Default);
  37. for(int i = 0; i < list.Count; i++)
  38. {
  39. Console.WriteLine("{0} ({1})",
  40. (list[i] as Person).Name, (list[i] as Person).Age);
  41. }
  42. Console.ReadKey();
  43. }
  44. }
  45. }
  示例的说明  在这个示例中,包含 Person 对象的 ArrayList 用两种不同的方式排序。调用不带参数的 ArrayList.Sort() 方法,将使用默认的比较方式,也就是使用 Person 类中的 CompareTo() 方法(因为这个类实现了 IComparable):

  1. public int Compare(object x, object y)
    {
    if(x is Person && y is Person)
    {
    return Comparer.Default.Compare(
    ((Person)x).Name, ((Person)y).Name);
    }
    else
    {
    throw new ArgumentException("One or both objects to compare are not Person objects.");
    }
    }


  这个方式首先检查其参数能否与 Person 对象比较,即该对象是否能转换为 Person 对象。如果遇到问题,就抛出一个异常。否则,就比较两个 Person 对象的 Age 属性。  接着,使用实现了 IComparerPersonComparerName 类,执行非默认的比较排序。这个类有一个公共的静态字段,以方便使用:

  1. public static IComparer Default = new PersonComparerName();


  它可以用 PersonComparerNameDefault 获取一个实例,就像前面的 Comparer 类一样。这个类的 Compareto() 方法如下:

  1. public int Compare(object x, object y)
    {
    if(x is Person && y is Person)
    {
    return Comparer.Default.Compare(
    ((Person)x).Name, ((Person)y).Name);
    }
    else
    {
    throw new ArgumentException("One or both objects to compare are not Person objects.");
    }
    }


  这里也是首先检查参数,看看它们是不是 Person 对象,如果不是,就抛出一个异常;如果是,就使用默认的 Comparer 对象比较 Person 对象的两个字符串字段 Name