使用集合

  System.Collections 名称空间中的类 System.Collections.ArrayList 也实现了 IList、ICollection 和 IEnumerable 接口,但实现方式比 System.Array 更复杂。数组的大小是固定不变的(不能添加或删除元素),而这个类可以用于表示大小可变的项列表。为了更准确地理解这个高级集合的功能,下面列举一个使用这个类和一个简单数组的示例。

  数组和高级集合:Ch11Ex01  (1)在 C:\BegVCSharp\Chapter11 目录中创建一个新的控制台应用程序 Ch11Ex01。  (2)在 解决方案资源管理器 窗口中右击项目,选择 Add | Class 选项,给项目添加 3 个新类: AnimalCowChicken。  (3)修改 Animal.cs 中的代码,如下所示:
  1. namespace Ch11Ex01
  2. {
  3. public abstract class Animal
  4. {
  5. protected string name;
  6. public string Name
  7. {
  8. get {
  9. return name;
  10. }
  11. set {
  12. name = value;
  13. }
  14. }
  15. public Animal()
  16. {
  17. name = "The animal with no name";
  18. }
  19. public Animal(string newName)
  20. {
  21. name = newName;
  22. }
  23. public void Feed()
  24. {
  25. Console.WriteLine("{0} has been fed.", name);
  26. }
  27. }
  28. }
  (4)修改 Cow.cs 中的代码,如下所示:
  1. namespace Ch11Ex01
  2. {
  3. public class Cow : Animal
  4. {
  5. public void Milk()
  6. {
  7. Console.WriteLine("{0} has been milked.", name);
  8. }
  9. public Cow(string newName) : base(newName)
  10. {
  11. }
  12. }
  13. }
  (5)修改 Chicken.cs 中的代码,如下所示:
  1. namespace Ch11Ex01
  2. {
  3. public class Chicken : Animal
  4. {
  5. public void LayEgg()
  6. {
  7. Console.WriteLine("{0} has laid an egg.", name);
  8. }
  9. public Chicken(string newName) : base(newName)
  10. {
  11. }
  12. }
  13. }
  (6)修改 Program.cs 中的代码,如下所示:
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using Syste.Threading.Tasks;
  6. namespace Ch11Ex01
  7. {
  8. class Program
  9. {
  10. static void Main(string[] args)
  11. {
  12. Console.WriteLine("Create an Array type collection of Animal " +
  13. "objects and use it:");
  14. Animal[] animalArray = new Animal[2];
  15. Cow myCow1 = new Cow("Deirdre");
  16. animalArray[0] = myCow1;
  17. animalArray[1] = new Chicken("Ken");
  18. foreach (Animal myAnimal in animalArray)
  19. {
  20. Console.WriteLine("New {0} object added to Array collection, " +
  21. "Name = {1}", myAnimal.ToString(), myAnimal.Name);
  22. }
  23. Console.WriteLine("Array collection contains {0} objects.", animalArray.Length);
  24. animalArray[0].Feed();
  25. ((Chicken)animalArray[1]).LayEgg();
  26. Console.WriteLine();
  27. Console.WriteLine("Create an ArrayList type collection of Animal " +
  28. "objects and use it: ");
  29. ArrayList animalArrayList = new ArrayList();
  30. Cow myCow2 = new Cow("Hayley");
  31. animalArrayList.Add(myCow2);
  32. animalArrayList.Add(new Chicken("Roy"));
  33. foreach (Animal myAnimal in animalArrayList)
  34. {
  35. Console.WriteLine("New {0} object added to ArrayList collection, " +
  36. "Name = {1}", myAnimal.ToString(), myAnimal.Name);
  37. }
  38. Console.WriteLine("ArrayList collection contains {0} objects.", animalArrayList.Count);
  39. ((Animal)animalArrayList[0]).Feed();
  40. ((Chicken)animalArrayList[1]).LayEgg();
  41. Console.WriteLine();
  42. Console.WriteLine("Additional manipulation of ArrayList:");
  43. animalArrayList.RemoveAt(0);
  44. ((Animal)animalArrayList[0]).Feed();
  45. animalArrayList.AddRange(animalArray);
  46. ((Chicken)animalArrayList[2]).LayEgg();
  47. Console.WriteLine("The animal called {0} is at index {1}.", myCow1.Name, animalArrayList.IndexOf(myCow1));
  48. myCow1.Name = "Janice";
  49. Console.WriteLine("The animal is now called {0}.", ((Animal)animalArrayList[1]).Name);
  50. Console.ReadKey();
  51. }
  52. }
  53. }
  示例的说明

  这个示例创建了两个对象集合,第一个集合使用 System.Array 类(这是一个简单数组),第二个集合使用 System.Collections.ArrayList 类。这两个集合都是 Animal 对象,在 Animal.cs 中定义。Animal 类是抽象类,所以不能进行实例化。但通过多态性(详见第 8 章),可以使集合中的项成为派生于 Animal 类的 CowChicken 类实例。

  在 Class1.csMain() 方法中创建好这些数组后,就可以显示其特性和功能。有几个处理操作可以应用到 ArrayArrayList 集合上,但它们的语法略有区别。也有一些操作只能使用更高级的 ArrayList 类型。

  下面首先通过比较这两种集合类型的代码和结果,讨论一下类似操作。首先是集合的创建。对于简单数组而言,只有固定的大小来初始化数组,才能使用它。下面使用第 5 章介绍的标准语法创建数组 animalArray

  1. Animal[] animalArray = new Animal[2];

  这个类还有另外两个构造函数。第一个构造函数把现有的集合作为一个参数,将其内容复制到新实例中;而另一个构造函数通过一个参数设置集合的容量(capacity)。这个容量用一个int值指定,设置集合中可以包含的初始项数。但这并不是绝对容量,因为如果集合中的项数超过了这个值,容量就会自动增加一倍。

  因为数组是引用类型(例如,AnimalAnimal 派生的对象),所以用一个长度初始化数组并没有初始化它所包含的项。要使用一个指定的项,该项还需要初始化,即需要给这个项赋予初始化了的对象:

  1. Cow myCow1 = new Cow("Deirdre");
  2. animalArray[0] = myCow1;
  3. animalArray[1] = new Chicken("Ken");

  这段代码以两种方式完成该初始化任务: 用现有的 Cow 对象来赋值,或者通过创建一个新的 Chicken 对象来赋值。主要区别在于前者引用了数组中的对象—我们在代码的后面就使用了这种方式。

  对于 ArrayList 集合,它没有现成的项,也没有 null 引用的项。这样就不能以相同的方式给索引赋予新实例。我们使用 ArrayList 对象的 Add() 方法添加新项:

  1. Cow myCow2 = new Cow("Hayley");
  2. animalArrayList.Add(myCow2);
  3. animalArrayList.Add(new Chicken("Roy"));

  除语法稍有不同外,还可以采用相同的方式把新对象或现有对象添加到集合中。以这种方式添加完项后,就可以使用与数组相同的语法来改写它们,例如:

  1. animalArrayList[0] = new Cow("Anima");

  但不能在这个示例中这么做。

  第 5 章介绍了如何使用 foreach 结构迭代一个数组。这是可以的,因为 System.Array 类实现了 IEnumerable 接口,这个接口的唯一方法 GetEnumerator() 可以迭代集合中的各项。后面将更加深入地讨论这一点。在代码中,我们写出了数组中每个 Animal 对象的信息:

  1. foreach (Animal myAnimal in animalArray)
  2. {
  3. Console.WriteLine("New {0} object added to Array collection, " +
  4. "Name = {1}", myAnimal.ToString(), myAnimal.Name);
  5. }

  这里使用的 ArrayList 对象也支持 IEnumerable 接口,并可以与 foreach 一起使用,此时语法是相同的:

  1. foreach (Animal myAnimal in animalArrayList)
  2. {
  3. Console.WriteLine("New {0} object added to ArrayList collection, " +
  4. "Name = {1}", myAnimal.ToString(), myAnimal.Name);
  5. }

  接着使用数组的 Length 属性,在屏幕上输出数组中元素的个数:

  1. Console.WriteLine("Array collection contains {0} objects.", animalArray.Length);

  也可以使用 ArrayList 集合得到相同的结果,但要使用 Count 属性,该属性是 ICollection 接口的一部分:

  1. Console.WriteLine("ArrayList collection contains {0} objects.", animalArrayList.Count);

  如果不能访问集合—无论是简单数组,还是较复杂的集合—中的项,它们就没什么用途。简单数组是强类型化的,可以直接访问它们所包含的项类型。所以可以直接调用项的方法:

  1. animalArray[0].Feed();

  数组的类型是抽象类型 Animal,因此不能直接调用由派生类提供的方法,而必须使用数据类型转换:

  1. ((Chicken)animalArray[1]).LayEgg();

  ArrayList 集合是 System.Object 对象的集合(通过多态性赋给 Animal 对象),所以必须对所有的项进行数据类型转换:

  1. ((Animal)animalArrayList[0]).Feed();
  2. ((Chicken)animalArrayList[1]).LayEgg();

  代码的剩余部分利用的一些 ArrayList 集合功能超出了 Array 集合的功能范围。首先,可以使用 Remove()RemoveAt() 方法删除项,这两个方法是在 ArrayList 类中实现的 IList 接口的一部分。它们分别根据项的引用或索引从数组中删除项。本例使用后一个方法删除列表中的第一项,即 Name 属性为 HayleyCow 对象:

  1. animalArrayList.RemoveAt(0);

  另外,还可以使用:

  1. animalArrayList.Remove(myCow2);

  因为这个对象已经有一个本地引用了,所以可以通过 Add() 添加对数组的一个现有引用,而不是创建一个新对象。无论采用哪种方式,集合中唯一剩余的项是 Chicken对象,可以通过以下方式访问它:

  1. ((Animal)animalArrayList[0]).Feed();

  当对 ArrayList 对象中的项进行修改,使数组中剩下 N 个项时,其索引范围为 0 ~ N-1。例如,删除索引为 0 的项,会使其他项在数组中移动一个位置,所以应使用索引 0(而非 1)来访问 Chicken 对象。不再有索引为 1 的项了(因为集合中最初只有两个项),所以如果试图执行下面的代码,就会抛出异常:

  1. ((Animal)animalArrayList[1]).Feed();

  ArrayList 集合可以用 AddRange() 方法一次添加好几项。这个方法接受带有 ICollection 接口的任意对象,包括前面的代码所创建的 animalArray 数组:

  1. animalArrayList.AddRange(animalArray);

  为了确定这是否有效,可以试着访问集合中的第三项,它将是 animalArray 中的第二项:

  1. ((Chicken)animalArrayList[2]).LayEgg();

  AddRange() 方法不是 ArrayList 提供的任何接口的一部分。这个方法专用于 ArrayList 类,证实了可以在集合类中执行定制操作,而不仅是前面介绍的接口要求的操作。这个类还提供了其他有趣的方法,如 InsertRange(),它可以把数组对象插入到列表中的任何位置,还有用于排序和重新排序数组的方法。

  最后,再回头来看看对同一个对象进行多个引用。使用 IList 接口中的 IndexOf() 方法可以看出, myCow1 (最初添加到 animalArray 中的一个对象)现在是 animalArrayList 集合的一部分,它的索引如下:

  1. Console.WriteLine("The animal called {0} is at index {1}.", myCow.Name, animalArrayList.IndexOf(myCow1));

  例如,接下来的两行代码通过对象引用重新命名了对象,并通过集合引用显示了新名称:

  1. myCow1.Name = "Janice";
  2. Console.WriteLine("The animal is now called {0}.", ((Animal)animalArrayList[1]).Name);