使用集合
System.Collections 名称空间中的类 System.Collections.ArrayList 也实现了 IList、ICollection 和 IEnumerable 接口,但实现方式比 System.Array 更复杂。数组的大小是固定不变的(不能添加或删除元素),而这个类可以用于表示大小可变的项列表。为了更准确地理解这个高级集合的功能,下面列举一个使用这个类和一个简单数组的示例。
数组和高级集合:Ch11Ex01 (1)在C:\BegVCSharp\Chapter11
目录中创建一个新的控制台应用程序Ch11Ex01
。 (2)在解决方案资源管理器
窗口中右击项目,选择Add | Class
选项,给项目添加 3 个新类:Animal
、Cow
和Chicken
。 (3)修改Animal.cs
中的代码,如下所示:
namespace Ch11Ex01
{
public abstract class Animal
{
protected string name;
public string Name
{
get {
return name;
}
set {
name = value;
}
}
public Animal()
{
name = "The animal with no name";
}
public Animal(string newName)
{
name = newName;
}
public void Feed()
{
Console.WriteLine("{0} has been fed.", name);
}
}
}
(4)修改Cow.cs
中的代码,如下所示:
namespace Ch11Ex01
{
public class Cow : Animal
{
public void Milk()
{
Console.WriteLine("{0} has been milked.", name);
}
public Cow(string newName) : base(newName)
{
}
}
}
(5)修改Chicken.cs
中的代码,如下所示:
namespace Ch11Ex01
{
public class Chicken : Animal
{
public void LayEgg()
{
Console.WriteLine("{0} has laid an egg.", name);
}
public Chicken(string newName) : base(newName)
{
}
}
}
(6)修改Program.cs
中的代码,如下所示:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Syste.Threading.Tasks;
namespace Ch11Ex01
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Create an Array type collection of Animal " +
"objects and use it:");
Animal[] animalArray = new Animal[2];
Cow myCow1 = new Cow("Deirdre");
animalArray[0] = myCow1;
animalArray[1] = new Chicken("Ken");
foreach (Animal myAnimal in animalArray)
{
Console.WriteLine("New {0} object added to Array collection, " +
"Name = {1}", myAnimal.ToString(), myAnimal.Name);
}
Console.WriteLine("Array collection contains {0} objects.", animalArray.Length);
animalArray[0].Feed();
((Chicken)animalArray[1]).LayEgg();
Console.WriteLine();
Console.WriteLine("Create an ArrayList type collection of Animal " +
"objects and use it: ");
ArrayList animalArrayList = new ArrayList();
Cow myCow2 = new Cow("Hayley");
animalArrayList.Add(myCow2);
animalArrayList.Add(new Chicken("Roy"));
foreach (Animal myAnimal in animalArrayList)
{
Console.WriteLine("New {0} object added to ArrayList collection, " +
"Name = {1}", myAnimal.ToString(), myAnimal.Name);
}
Console.WriteLine("ArrayList collection contains {0} objects.", animalArrayList.Count);
((Animal)animalArrayList[0]).Feed();
((Chicken)animalArrayList[1]).LayEgg();
Console.WriteLine();
Console.WriteLine("Additional manipulation of ArrayList:");
animalArrayList.RemoveAt(0);
((Animal)animalArrayList[0]).Feed();
animalArrayList.AddRange(animalArray);
((Chicken)animalArrayList[2]).LayEgg();
Console.WriteLine("The animal called {0} is at index {1}.", myCow1.Name, animalArrayList.IndexOf(myCow1));
myCow1.Name = "Janice";
Console.WriteLine("The animal is now called {0}.", ((Animal)animalArrayList[1]).Name);
Console.ReadKey();
}
}
}
示例的说明
这个示例创建了两个对象集合,第一个集合使用 System.Array
类(这是一个简单数组),第二个集合使用 System.Collections.ArrayList
类。这两个集合都是 Animal
对象,在 Animal.cs
中定义。Animal
类是抽象类,所以不能进行实例化。但通过多态性(详见第 8 章),可以使集合中的项成为派生于 Animal
类的 Cow
和 Chicken
类实例。
在 Class1.cs
的 Main()
方法中创建好这些数组后,就可以显示其特性和功能。有几个处理操作可以应用到 Array
和 ArrayList
集合上,但它们的语法略有区别。也有一些操作只能使用更高级的 ArrayList
类型。
下面首先通过比较这两种集合类型的代码和结果,讨论一下类似操作。首先是集合的创建。对于简单数组而言,只有固定的大小来初始化数组,才能使用它。下面使用第 5 章介绍的标准语法创建数组 animalArray
:
Animal[] animalArray = new Animal[2];
这个类还有另外两个构造函数。第一个构造函数把现有的集合作为一个参数,将其内容复制到新实例中;而另一个构造函数通过一个参数设置集合的容量(capacity
)。这个容量用一个int值指定,设置集合中可以包含的初始项数。但这并不是绝对容量,因为如果集合中的项数超过了这个值,容量就会自动增加一倍。
因为数组是引用类型(例如,Animal
和 Animal
派生的对象),所以用一个长度初始化数组并没有初始化它所包含的项。要使用一个指定的项,该项还需要初始化,即需要给这个项赋予初始化了的对象:
Cow myCow1 = new Cow("Deirdre");
animalArray[0] = myCow1;
animalArray[1] = new Chicken("Ken");
这段代码以两种方式完成该初始化任务: 用现有的 Cow
对象来赋值,或者通过创建一个新的 Chicken
对象来赋值。主要区别在于前者引用了数组中的对象—我们在代码的后面就使用了这种方式。
对于 ArrayList
集合,它没有现成的项,也没有 null
引用的项。这样就不能以相同的方式给索引赋予新实例。我们使用 ArrayList
对象的 Add()
方法添加新项:
Cow myCow2 = new Cow("Hayley");
animalArrayList.Add(myCow2);
animalArrayList.Add(new Chicken("Roy"));
除语法稍有不同外,还可以采用相同的方式把新对象或现有对象添加到集合中。以这种方式添加完项后,就可以使用与数组相同的语法来改写它们,例如:
animalArrayList[0] = new Cow("Anima");
但不能在这个示例中这么做。
第 5 章介绍了如何使用 foreach
结构迭代一个数组。这是可以的,因为 System.Array
类实现了 IEnumerable
接口,这个接口的唯一方法 GetEnumerator()
可以迭代集合中的各项。后面将更加深入地讨论这一点。在代码中,我们写出了数组中每个 Animal
对象的信息:
foreach (Animal myAnimal in animalArray)
{
Console.WriteLine("New {0} object added to Array collection, " +
"Name = {1}", myAnimal.ToString(), myAnimal.Name);
}
这里使用的 ArrayList
对象也支持 IEnumerable
接口,并可以与 foreach
一起使用,此时语法是相同的:
foreach (Animal myAnimal in animalArrayList)
{
Console.WriteLine("New {0} object added to ArrayList collection, " +
"Name = {1}", myAnimal.ToString(), myAnimal.Name);
}
接着使用数组的 Length
属性,在屏幕上输出数组中元素的个数:
Console.WriteLine("Array collection contains {0} objects.", animalArray.Length);
也可以使用 ArrayList
集合得到相同的结果,但要使用 Count
属性,该属性是 ICollection
接口的一部分:
Console.WriteLine("ArrayList collection contains {0} objects.", animalArrayList.Count);
如果不能访问集合—无论是简单数组,还是较复杂的集合—中的项,它们就没什么用途。简单数组是强类型化的,可以直接访问它们所包含的项类型。所以可以直接调用项的方法:
animalArray[0].Feed();
数组的类型是抽象类型 Animal
,因此不能直接调用由派生类提供的方法,而必须使用数据类型转换:
((Chicken)animalArray[1]).LayEgg();
ArrayList
集合是 System.Object
对象的集合(通过多态性赋给 Animal
对象),所以必须对所有的项进行数据类型转换:
((Animal)animalArrayList[0]).Feed();
((Chicken)animalArrayList[1]).LayEgg();
代码的剩余部分利用的一些 ArrayList
集合功能超出了 Array
集合的功能范围。首先,可以使用 Remove()
和 RemoveAt()
方法删除项,这两个方法是在 ArrayList
类中实现的 IList
接口的一部分。它们分别根据项的引用或索引从数组中删除项。本例使用后一个方法删除列表中的第一项,即 Name
属性为 Hayley
的 Cow
对象:
animalArrayList.RemoveAt(0);
另外,还可以使用:
animalArrayList.Remove(myCow2);
因为这个对象已经有一个本地引用了,所以可以通过 Add()
添加对数组的一个现有引用,而不是创建一个新对象。无论采用哪种方式,集合中唯一剩余的项是 Chicken
对象,可以通过以下方式访问它:
((Animal)animalArrayList[0]).Feed();
当对 ArrayList
对象中的项进行修改,使数组中剩下 N
个项时,其索引范围为 0 ~ N-1。例如,删除索引为 0 的项,会使其他项在数组中移动一个位置,所以应使用索引 0(而非 1)来访问 Chicken
对象。不再有索引为 1 的项了(因为集合中最初只有两个项),所以如果试图执行下面的代码,就会抛出异常:
((Animal)animalArrayList[1]).Feed();
ArrayList
集合可以用 AddRange()
方法一次添加好几项。这个方法接受带有 ICollection
接口的任意对象,包括前面的代码所创建的 animalArray
数组:
animalArrayList.AddRange(animalArray);
为了确定这是否有效,可以试着访问集合中的第三项,它将是 animalArray
中的第二项:
((Chicken)animalArrayList[2]).LayEgg();
AddRange()
方法不是 ArrayList
提供的任何接口的一部分。这个方法专用于 ArrayList
类,证实了可以在集合类中执行定制操作,而不仅是前面介绍的接口要求的操作。这个类还提供了其他有趣的方法,如 InsertRange()
,它可以把数组对象插入到列表中的任何位置,还有用于排序和重新排序数组的方法。
最后,再回头来看看对同一个对象进行多个引用。使用 IList
接口中的 IndexOf()
方法可以看出, myCow1
(最初添加到 animalArray
中的一个对象)现在是 animalArrayList
集合的一部分,它的索引如下:
Console.WriteLine("The animal called {0} is at index {1}.", myCow.Name, animalArrayList.IndexOf(myCow1));
例如,接下来的两行代码通过对象引用重新命名了对象,并通过集合引用显示了新名称:
myCow1.Name = "Janice";
Console.WriteLine("The animal is now called {0}.", ((Animal)animalArrayList[1]).Name);