对着月亮唱歌的幸福生活

即便是莲藕内心真空之所,也有根根柔丝穿过。
  博客园  :: 首页  :: 联系 :: 管理

Object-Oriented: C#的群集类

Posted on 2008-07-31 09:54  对月而歌  阅读(719)  评论(0)    收藏  举报

    我们有一种需求,即有必要在创建对象的时候迅速收集他们的引用,从而能遍历并获得其中一个特定的对象,等等.在面向对象变成语言中,可以一种被称为群集(collection)的特殊类型做到这一点.C#最简单的群集类型是定长数组,数组是对象;而Systen.Array是所有数组的基础类.在本文中,我们回顾所有有关数组的内容,并介绍System.Array类的一些更有意思的特征.

     当程序运行时很难预料会创建多少个对象,用定长数组存储数量不定的对象,效率太低.System.Conllections命名空间定义了多种可选群集类,我们将会用他们构造SRS-那就是ArrayList类和Hashtable类.

     

注意,System.Array类不是System.Conllections命名空间的一部分;不过,多有的C#群集类----System.Array和各种System.Conllection类----都实现了ICollection接口,该接口在System.Collecton命名空间中定义,所以,所有的群集都共享一系列公共行为.

 

 1.数组

    System.Array类定义了一系列有用的发方法和property来搜索,排序和修改数组,以及查询长度.

1.数组的Length property

    最常用的数组property是int类型的Length property,用来表示数组中所有元素的数量.下面代码演示了如何用Length来求的一维数组的长度:

    int[] x = new int[20];

    //数组初始化过程已从略... ...

     //遍历数组.

     //在i等于x.Length之前结束!!!

     for(int i = 0;i<x.Length;i++){

          Console.WriteLine(x[i]);

          }

     数组从零开始记数,因此,在for循环中,必须在达到长度上限之前停止,如上例所示.

     注意,数组的长度并不反映数组中有多少个元素被赋了值,从技术上说,即便没有明确地在数组中存储任何东西,每个元素都会被自动填充该数组类型中等价于零的值.因此,数组的长度仅仅表明了整个数组的容量;它的容量在一开始声明后被固定了,以后也在不能更改.

2.数组的方法

     Array类定义了许多游泳的景泰方法和实体方法,可以用来检查或操作数组;接下来我们将讨论几个常用的方法.关于Array类方法的完整描述请参阅MSDN网站的FCL参考.

     前三个方法是静态的.以为着他们必须从作为一个整体Array类上调用:   

  • public static void Clear (Array array,int startIndex,int length);将数组知道能够范围内的元素重设为与零等价的值.参数包括要清零的数组名称,开始位置(从0开始计数)和需要清零的元素目录;
    int[] x = new int [5];
    Array.Clear(x,0,x.Length);
  • public static void Reverse(Array array):调转一个一维数组中的所有元素的次序;
    int [] x={1,2,3};
    Array.Reverse(x);
    //现在x中的元素包括:{3,2,1}.
    该方法还有一个覆载版本,能掉转从起始位置开始的规定长度的元素次序:
    public static void Reverse(Array array,int startIndex,int length)
  • public static void Sort(Array array):对一维数组中的元素进行排序.对字符串默认的排序规则是按字母表次序,对数字则按从小到大排序;
    string [] names = {"Vijay","Tiger","Phil"};
    Array.Sort(names);
    //元素的次序现在变成了"Phil","Tiger","Vijay".
    该方法有一个覆载版本,能对从其始位置开始规定长度内的元素进行排序:
    public static void Sort(Array array, int stratIndex, int length)
    *还有一种排序方式,即用户自定义排序规则,但是超出了本书的讨论范围.
  • 接下来三个方法是实体方法,以为着他们能经由不同的数组实体直接被调用:
  • public object GetValue(int index):以一般对象的形式返回一维数组中鼎鼎位置上的元素.如果我们确知返回对象的类型,就能转换它:
    String[] names = {"Chris","Sandi","Brennan"};
    //将返回对象转换为字符串.
    string name = (string) names.GetValue(0);

    int [] numbers ={11,17,85};
    //将返回对象转换为整数
    string number = (int) number.GetValue(0);
    该方法也有覆载版本,能够返回二维或者三维数组中元素:
    public object GtValue(int index1,int index2)
    public object GtetValue(int index1,int index2,int index3)
  • public void SetValue(object value , int index);修改一维数组中指定位置上的元素;
    该方法还有修改二维数组或三维数组中指定元素值的覆载版本:
    public void SetValue(object value,int index1,int index2,int index3)
    *虽然GetValue和SetValue是Array类提供的方法,但索引器的概念更为容易和便与使用.
    语法
         string name = name[0];
    比下面等同的语法更易编写和理解
         string name = (string) names.GetValue(0);
  • public int GetLength(int dimension):返回任意数组中指定维度的长度.例如,要得到一个二维数组中第一维的长度,用GetLength(0).(对于一维数组,只需用Length property来判断长度.) 

2.ArrayList类

0.ArrayList是一个简单的,动态改变长度的一维有序列表,允许存储不同数量的对象引用,而不必操心容器长度.ArrayList在逻辑上等同于一维数组,但长度能随需应变.

     ArrayList能够包含任意类型的元素(因为在C#所有种类元素都是对象),而且,如果有必要,还可能在同一个ArrayList中混合放置这些不同类型的元素.

     ArrayList类在System.Collections命名空间中的定义.要引用ArrayList类,在代码短员开头放置以下using知识符:

     using System.Collections;

     或者使用全名:

     System.Collectiongs.ArrayList.

1.创建一个ArrayList

     可以使用ArrayList类提供的三个构造器之一实体化一个ArrayList对象.

 

  • 最简单的形式是无参数构造器:
    ArrayList coursesTaken = new ArrayList();
    该构造器创建一个内空的,缺省容另为16个元素的ArrayList.当元素数量达到当前数量上限时,ArrayList的容量自动翻倍.
  • 第二种形式是接受一个整数参数,该参数代表ArrayList的初始容量;如:
    ArrayList students = new ArrayList(400);
    当我们确知ArrayList中将会放入最大对象引用时,应用这种形式的构造器.把ArrayList初始容量定为大于缺省的16个元素,减少了从新设定容器的次数,从而提升了性能.
  • 第三种构造器接受任何合适的C#群集对象----即,所有实现恶劣ICollection接口的类的对象,包括Systen.Collections类的对象和System.Array类的对象----作为参数,然后复制传入群集的内容,组装为新构造的ArrayList对象.
    //创建一个Course对象数组 
    Course[] course = new Course [3];
    Course[
    0= new Course ("Math 101");
    Course[
    1= new Course("Physics 250");
    Course[
    2= new Course("Management 283");
    //其他细节从略
    //在后面的饿程序中,用它来初始化一个ArrlyList.
    ArrayList coursesTaken = new ArrayList(course);

    在上面的代码片段中,我们初始化了一个ArrayList,容纳一个Course数组.注意俩个群集都是指向相同的对象.

2.ArrayList property

     ArrayList 声明了多个attribute来返回自身信息.

  • Capacity property,用来得到ArrayList的容量:(如果试图在代码中将他的长度减小到不能容纳已有元素时,一个ArgumentOutOfRangeException异常会被抛出)
  • Count property ,ArrayList中当前的元素数量;
  • 为了得到一个ArrayList中的某个元素,ArrayList类定义恶劣一个特殊的索引器property,允许我们象普通数组一样访问里面的元素----在方括号中输入要索引的整数值,索引器返回一个普通对象引用,被索引的元素被转换为原本类型.
    //这个例子演示了这些property
    Course c1 = new Course("Math 101");
    Course c2 
    = new Course("Physics 250");
    Course c3 
    = new Course("Management 283");
    //正确地使用ArrayList索引器(注意,看起来象一个数组!)
    coursesTaken[0= c1;
    coursesTaken[
    1= c2;
    coursesTaken[
    2= c3;
    //使用Capacity和Count property
    Console.WriteLine("capacity = " +coursesTaken,Capacity);
    Console.WriteLine(no. of elements 
    =" +coursesTaken.Count);
    //用索引器取出ArrayList中的元素
    for (int i = 0;i<coursesTaken.Count;i++){
       
    //用索引器取出ArrayList中的元素
       
    //将一个普通对象重新转换为Course对象引用
       Course c = (Course) courseTaken[1];
       
    //在c上调用Course方法
       Console.WriteLine(c.Name);
    }

    这个例子说明。在ArrauLis中加入不同类型的元素是很危险的。ArrayList中的元素被遍历转换为Course对象。每个Course对象中 Name property被调用。如果ArrayList中每一个元素都是Course对象(或者Course类派生的类型对象(),就能正常工作。但是如果不同类型的元素存在,就会抛出一个InvalidCastException异常.在本章稍后部分“集群与转换 ”一节中,我们将会看到更多混合类型导致错误的例子。
3. ArrayList类的方法

    常用的ArrayList方法如下:

  • public virtual int Add(object value):在ArrayList末尾添加一个对象引用,若容器不足自动扩展。前面virtual关键字表示允许派生类覆载这个方法。返回一个int类型,表示传如参数在ArrayList中的位置。
  • public void Add(ICollection c);在ArrayList末尾添加特定群集的内容;
  • public void SetRange(int starIndex,ICollection c): 从指定其始处用另一ICollection对象取代ArrayList原有元素。c中的元素大雨ArrayList将会自动扩展。
  • public IEnumerator Get Enumerator();获取一个IEnumerator(一种特殊类型的类型对象)对象,可用来遍历ArrayList中的元素。我们将在本章后面部分讨论IEnumerator;
  • public int IndexOf(object obj):寻找一个指定的对象引用,如果找到,则返回第一个匹配位置(从0开始计数)。如果没找到返回-1;
  • piblic bool Contains(object obj):寻找一个指定的对象引用,如果找到,返回true,否则返回false;
  • public void RemoveAt(int index):将指定位置的元素剔除,并且“填上”空洞。Count property将会递减,而Capacity则保持不变。如果指定的索引大于Count值或者小于0,将抛出一个ArgumentOutPfRangeException异常
  • public void Remove(object obj): 在ArrayList中搜寻指定的对象引用,如果找到,则把第一个找到的对象从ArrayList中移除。Count property将会减1,而Capacity则会保持不变,如果希望删除掉所有obj,在一个while循环中组合使用Contains和Remove方法:
    while(arraylist.Contains(x)){
         arraylist.Remove(x)
         }
  • public void Sort(); 对ArrayList中的元素排序。默认按照字母排序对string 元素排序,按照从小到大顺序对数值型元素排序;
    *该方法还有一个覆载版本,允许拥护定制对任意类型的排序算法,但对定制排序算法的讨论已经超出本书的范围了。
  • public void Clear();清空整个ArrayList.Count property 被设为0,Capacity不变;
  • public object[] ToArray();创建一个Object数组,并将ArrayList中的元素拷贝进该宿主。

     ArrayList类中还有很多方法!如果需要完整的ArrayList方法列表,请参考阅读MSDN网站上的FCL参考。

3.Hashtable类

     Hastable类提供了c#中另外一种群集管理对象的引用途径。Hastable比ArrayList略显复杂,因为它允许我们用唯一关键词直接访问指定对象;Hastable是字典群集类饿一种实现,关键次和对象本身可以是任何一种类型;关键词经常是但非一定是string类型。

     和ArrayList一样,Hastable可以在System.Collections的命名空间中找到。要使用Hastable简称,需要在源程序头部加上using System.Collection知指示符,或者用它的全名----System.Collections.Hashtable

1.可以使用Hastable类中声明的任何一个构造器创建Hashtable对象。实例化Hastable的最简单手段,是用无参数的构造器创建一个空的Hastable ,我们使用Add方法插入所需要的对象,方法头如下:

public void Add(object key ,object value)

     注意,必须为添加到Hastable的没个元素指定一个关键词,通过它能在以后重新得到该元素;在本例中,使用一个string对象作为关键词----学生的社会保障号(Ssn):

 

 


//创建一个Hastable实体(卡蛋板)
Hastable students = new Hastable();
//创建多个Student对象(“鸡蛋”)。
Student s1 = new Student("123-45-6789","John Smith");
Student s2 
= new Student("987-65-4321","Mary Jones");
Student s3 
= new Student("654-32-1987","Jim Green");
//在Hastable中存储这些Student对象的句柄,
//用每个Student对象的Ssn property
//(恰好被声明为string)作为没项的关键词。
students.Add(s1.Ssn,s1);
students.Add(s2.Ssn.s2);
students.Add(s3.Ssn,s3);
    
//Hashtable类定义了一个索引器,用来获取或设置Hastable中的元素。同使用ArrayList一样,索引器通过一对放括号表示;但在Hashtable中,放括号里
//面放置元素的关键词:
//注意,和ArrayList一样,得到对象后要进行转换。
Student s = (Student) students[["123-45-6789"];//得到对象引用,表示学生Hohn Smith
Console.WriteLine("name is " + s.Name);
//或者。如另一个例子:
//伪代码。
string id = retrieve student ID number from a GUI;
Student s 
= (Student) students[id];

    注意,如果希望按对象的原来类型去操作从Hashtable的到的对象(本例中是一个Student 对象),必须惊醒转换操作,就象从ArrayList中得到一个对象一样。

(索引器返回一个普通的对象引用,被索引的元素被转换为原本的类型)

2.Hashtable类的property

    Hashtable和ArrayList一样,拥有Count  prperty和索引器,使用方法也相同。除次之外,Hashtable类还声明了许多特有的prperty

  • Keys property:返回一个ICollection对象,由Hashtable中的所有关键词组成
  • Values property:返回一个ICollection对象,由Hastable中所有关键词的值组成

 3.Hashtable类中的方法

    Hashtable类中常用的方法如下:

  • piblic void Add(object key,object value ):前面已提到,该方法使用第二个参数表示的对象引用作为关键词,把第一个对象应用插入到Hashtable中。如果在该关键词标注的位置已经有对象存在,将抛出一个ArgumentException异常。因此,应该先使用后面的ContainKey方法,确认该关键词没有被使用过,这非常重要。
  • public bool ContainsKey(object key): 如果指定关键词标注的条目在Hashtable中存在则返回true,反之返回false
    下面演示了组合ContainKey方法与Add方法,确保不尝试覆盖Hashtable中已存在的条目:
    Student s = new Student ("111-11-1111","Arnold Brown);
    if (stdents.ContainsKey(S.Ssn)){
    //哇!有重复,我们需要决定怎么办
    //细节从略
    }

    else{
    students.Add(s.Ssn,s);
    //Ok,因为没有发现重复。
    }

  • public bool ContainsValue(object value):寻找指定的值,不考虑其关键词,如果找到则返回true,反之则返回false;
  • public void Remove(object key):从Hashtable中移除指定关键词对应的对象引用;注意,对象本身存在于Hashtable以外,不会受到影响;
  • public IDictionaryEnumerator GetEnumerator();返回一个IDicitonaryEnumerator对象,可用来检测目前在Hashtable中存储的“关键词-值”对,我们将在下一节讨论枚举器(enumerators);
  • public void Clear();清空Hashtable,恢复到初始状态。Count property设为0。

4.集群和转换

    在C#群集中,唯有Array可以限制插如Object的类型.即声明 一个Array时,必须声明插入项的类型:

    int [] x = new int[20];//限制x只能是int类型.

    而在c#中的其他正式群集中,声明群集时,不能限制插入的Object类型;在前面提到,c#群集,除Array外,都被设计成能持有一般对象,因此,如果打算利用多太的好处,在想群集插入对象时,就唏嘘确保被插入的对象同文,同种.c#编译器不会组织我们把类型不一致的对象插入群集中,但如果在取出对象后试图"不正确地"操作对象,c#编译器或运行环境就会报错.

    为了演示这个概念,我们看一个简单的例子.我们将使用一个 ArrayList群集类,但同样的概念对于c#其

System.Collection类也管用.假定定义了一个Student类,其中声明一个CalculateGPA方法.

public class Student
{
//细节从略.
 public void CalculateGPA(){
    
//细节从略.
    }
}
    
//再声明一个Professor类,但不声明CalculateGPA方法
public class Professor{
   
//细节从略----没声明ClaculateGPA方法
     }
   
//我们在前面已经学的到,往同一ArrayList中天家不同的Student和Professor对象,编译器不会报错,因为非Array群集
//并不限制添加项的类型:
Stidemt s = new Student();
Student t 
= new Student();
Professor p 
= new Professor();
Professor q 
= new Professor();
//不能将ArrayList声明为特定类型.
ArrayList people = new ArrayList();
//在群集中混合加入Student和Professor对象引用
people.Add(s);
people.Add(p); 
people.Add(q);
people.Add(t);
people[
0].CalculateGPA();

    做为程序的编写者,我们知道,ArrayList零元素位置上的元素是一个Student,所以让我们通过索引器来得到这个元素,并调用其CalculateGPA方法:

people[0].CalculateGPA();

    这样做看起来没错,但试图编译时就会产生一个错误:

     Error CS0117:'Object' does not contain  for 'CalculateGPA' 的定义

    即便ArrayList的零元素在运行时就是Student对象,编译器也只认为索引器返回的是一个一般对象引用.在编译器眼中,people[0] 只是一个一般对象引用,而且该对象没有声明CalculateGpa方法----只有Student类声明过这个方法----这行无法编译.

    解决这个编译错误的办法是,在调用CalculateGPA方法之前,强制转换ArrayList索引器的返回值为Student对象.

    Student s2 = (Student) people[0];

    s2.CalculateGPA();

或者写成一行代码

    ((Student) people[0]).CalculateGPA();

    上面2种手段,都是编译器可以接受的,因为我们要求编译器相信,在运行时,提及的对象引用的确志向一个Student对象;编译器知道, Student类定义了一个CaculateGPA的方法,所以就万事大吉了!

    但这样仍有一个潜在的问题:如果ArrayList群集混合存储了Student和Professor对象引用,且我们想遍历整个群集,这样就不能把每个元素转换为Student对象,并调用CalculateGPA方法,因为Professor类并未定义CalculateGPA方法:

    for (int i = 0; i<people.Count; i++){

        Student x = (Student) people[i];//第i个元素不一定是Student对象引用!

          }

    上面的代码可以编译通过,因为我们再一次要求编译器相信,群集中所有元素都是Student对象;但在实际执行时,当遇到第一个Professor引用,就会抛出一个InvalidCastException异常,程序也将停止执行,因为根本没办法把 Professor类转换成Student类.如果打算在一个循环结构中遍历真个混杂了Professor对象和Student读乡的people群集,只能如此这般:

    *把所有元素看作一般对象(generic objects);

    *把所有元素转换为一个公共超类(common supertype).

    我们研究一下上述俩中手段.

    如果把群集中所有对象看作一般对象,那么编译器允许调用的每个object应用的唯一方法,只是Object类中已定义的方法:

    for (int i = 0;i <people.Count;i++){

      //不能转换对象引用.

      object x = people[i];

      //因此唯一能调用的x方法是Object类定义的方法.

      Console.WriteLine(x.ToString());

    然而,如果群集中的不同对象类型拥有公共超类----比方说Student和Professor都派生字共同的Person基类----就可以将所有对象引用都转换成Oerson对象的引用,因为根据继承的"is a"特点,Student对象是Person对象,Professor对象也是Person对象.在这种情况下,我们可以调用对象在Person类中定义的共有方法:

    for( int i = 0;i< people.Count;i++){

      Person x = (Person) people[i];//转换为一个公共超类.

      x.printAddress();//在Person中总定义的一个方法

      }

      System.Collections 群集,例如ArrayList或Hashtable,将插入的所有项视为一般对象引用,所以,程序员必须记得存放到群类集中的每个对象引用类型,这样,当重新获取他们时,才能够转换为正确的(公共基类)类型.一个助记的方法是,在群集生命后面加上注释,指出在其中存放的对象引用类型:

    ArrayList coursesTaken;//Course 类型的对象引用.

5.用枚举器 (Enumerator)遍历群集:

    例如我们可能需要获得一个ArrayList中所有Student对象引用,从而计算出学期的最后成绩.一种遍历ArrayLsit的方法是用for循环:

    ArrayList enrollment = new ArrayList();// Student对象引用群集

   //...细节从略

  //遍历整个ArrayList并处理所有学生成绩.

     通过这种方式处理ArrayList中的每个元素,是可能有的,因为我们能够经由索引定位到ArrayList中的一个指定元素:例如enrollment[i].

    另外还有一种遍历System.Collections群集中每个元素的手段,即使用一种化名叫枚举器(enunerator)的特殊对象类型.枚举器是一种遍历群集的机制,当得到一个群集的枚举器时----比如一个ArrayList的枚举器----可以概念性地任务它复制了原始群集中的内容,就好象为了做电话营销而复印某人的通讯录(Person对象引用群集)

    然后,我们遍厉每份"复印件"(),"在每个句号上打叉"(处理每个对象引用了直至到达枚举器末尾).

for (int i = 0;i<enrollment.Count;i++){
      Student s 
= (Student) enrollment[i];
      s.ComputeFinalGrade();
      }

1.IEnumerator接口

    前面提到过,ArrayList类通过以下头语句定义了一个GetEnumerator方法:

public virtual IEnumerator GetEnumerator()

    该方法返回的类型,IEnumerator,是一个定义在System.Collections命名空间中的接口.IEnuerator对象被调用时,表现为一个ArrayList的临时拷贝,拥有以下方法:

  • bool MoveNext();使用枚举器指向群集中下一个元素.如果成功指向则返回tru,如果到达群集尾则返回false
  • void Reset(): 将枚举器指向第一个值的前面位置,这样,下次调用MoveNext方法时,游标将定位到群集中的第一个元素.

下例演示了如何使用IEnumerator中的功能.我们使用以下简单版本的Course类:

 

public class Course
{//字段
  private string name;
  
//构造器
  public Course(string n){
    name 
= n ;
    }

    
//Property.
    public string Name{
    
//细节从略
     }

}

//下面代码用与驱动程序运行:
using System.Collectiongs;
public class EnimDemo
{
 
static void Main(){
    ArrayList coursesTaken 
= new ArrayList();
    Course c1 
= new Course("Math 101");
    Course c2 
= new Course("Physics 250");
    Course c3 
= new Course("Management 283");

    
//在ArrayList中插入Course对象引用.
     courseTaken.Add(c1);
     courseTaken.Add(c2);
     courseTaken.Add(c3);
      
    
//为ArrayList创建一个枚举器
    IEnimerator enum = courseTaken.GetEnumerator();
    
//使用枚举器遍历群集
    while (enum.MoveNext()){
      
//用Currnt property得到当前元素,然后将其转换为Course对象引用
       Course c = (Course)enum.Current;
      
//现在可以操作Course对象一样操作c.
      Console.WriteLine(c.Name);
      }

   }

}

程序运行输出以下结果:
Math 
101
Physics 
250
Management 
283

2.IDictionaryEnumerator接口

    我们已经了解到,Hashtable是用"名--值"对形式存储数据的一种群集.对于Hashtable有一种特殊的枚举器--IDictionaryEnumerator.前面提到过,Hashtable类声明了以下方法,来为给定的Hashtable得到一个IDictionaryEnumerator:

public virtual IDictionaryEnumerator GetEnumerator()

    IDictionaryEnumerator接口派生自IEnumerator类,并继承了我们之前讨过的MoveNext和Reset方法,IDictionaryEnumerator接口还定义了几个特有property,特别是Key和Value,他们分别返回当前元素的关键词和对应的值.

    下面是一个通过IDictionaryEnumerator得到Hashtable中元素的例子.我们从Student类的简单版本开始:

 

public class Student
{
  
private string name;
  
private string ssn;
  
//构造器
   public Student(strig s ,string n){
     ssn 
= s;
     name 
= n;
     }
   
//property
    public string Name{}//细节从略
    public string Ssn{}//细节从略
}
//下面是IDictionaryEnumerator的例子:
using System;
using System.Collections;

public class EnumDemo2
{
  
static void Main(){
    
//创建一个Hashtable实体。
    Hashtable student()
     
//实体化几个Student对象
      Student s1 = new Student("123-45-6789","Chris Williams");
     Student s2 
= new Student("987-65-4321","Yukio Koyari");
     Student s3 
= new Student("654-32-1987","Maria Lopez");
     
//将他们存储在Hashtable,使用Ssn作为关键词
      student.Add(s1.Ssn,s1);
     student.Add(s2.Ssn,s2);
     student.Add(s3.Ssn,s3);
     
//为Hashtable创建一个IDictionaryEnumerator
     IDictionaryEnumerator遍历群集。
      
while(enum.MoveNext()){
     
//从群集中得到下一个Student对象。
      Student s = (Student) enum.Value;
     Console.WriteLine(s.name
+":"+s.Ssn);
//运行程序将输出以下结果:
Maria Loez:654-32-1987
Yukio Koyari:
987-65-4321
Chris Williams:
123-45-6789
}
    注意,打印出来的学生信息,和当初添加到Hashtable中的顺序并不一样。通过IDictionaryEnumerator不能按照特定顺序遍历Hashtable;只能保证遍历所有项.

6.遍历一般ICollections

    记得前面讨论Hashtable时提到过,ICollection有俩个property--Keys和Values.让我们看看如何遍历一般ICollection中的元素.

    ICollection对象只有一种名为CopeTo的方法,方法头如下:

void CopyTo(Array array,int index)

    CopyTo方法用来将一个ICollections的内容拷贝到一个Array对象中.回忆前面的内容就会想起来,Array类是c#中多有数组类型的父类.index参数指明拷贝起使处.一旦关键词或值被传入数组,数组中的元素就能用普通手段访问.下面是一个通过关键词和对应值遍历Hashtable中元素的例子,我们使用前面定义的Student类,但驱动代码不同.

 

using System;
using System.Collections;

public class EnumDemo3
{
 
static void Main(){
   
//创建一个Hashtable实例.
   hashtable students = new Hashtable();
   
//初始化几个Student对象
     Student s1 = new Student("123-45-6789","John Smith");
    Student s2 
= new Student("987-65-4321","Yukio Koyari");
    Student s3 
= new Student("654-32-1987","Maria lopez");
   
//将他们存储在Hashtable,使用Ssn做为键名
    student.Add(s1.Ssn,s1);
    student.Add(s2.Ssn,s2);
    student.Add(s3.Ssn,s3);
   
//使用Keys和Values property,从hashtable创建俩个ICollection,
    ICollection keys = students.Keys;
    ICollection values 
= students.Values;
   
//创建string 和Student数组,保存keys和values.
    string [] studentIDs = new string[students.Count];
    Student[] studentObjects 
= new Student[students.Count];
   
//将ICollections呢容拷贝进数组.
    keys.CopyTo(studentIDs,0);
    values.CopyTo(studentObjects,
0);
   
//遍历
     Console.WriteLine("student IDs(key):");
      
for(int i = 0 ;i<studentIDs.Length;++i){
        Console.WriteLine(
"Student ID:"+studentID[i]);
         }

    Console.WriteLine(Students (Values):
");
      for(int i = 0;i<studentObjects.Length;++i){
        Console.WriteLine(s.Name
+":"+s.Ssn);
         }

  }

}

程序运行输出以下结果:
  Student IDs(Keys):
  Student ID: 
654-32-1987
  Student ID: 
987-65-4321
  Student ID: 
123-45-6789

  Student (Values):
  Maria Lopez: 
654-32-1987
  Yukio Koyari: 
987-65-4321
  John Smith:   
123-45-6789
}

7.foreach循环

    Foreach循环是C#控制结构流程之一.foreach循环提供了遍历数组或ICollection中元素的另一种手段.

    foreach循环的一般语法如下:

   foreach (type variablem_name in collection_name){

      //将要执行的代码

      }

    在foreach关键字后面的圆括号中,我们发现

  • 将被处理的群集中条目的类型----从群集中得到的条目被自动转换成该类型;
  • 与群集中的条目构成一对一引用关系的局部变量
  • 要搜索的群集名称,前面加上int关键字

例如:一个int数组

 

int [] numbers = {1,3,5,7,9};
int sum = 0 ;
foreach (int x in numbers){
    
//可以对x做任何事;注意不需要转换类型;
    
//x被自动转换为int
    Console.WriteLine("Adding"+x+ "to sum"):
    sum 
+= x;
    }
//上面的代码将产生以下输出
Adding 1 to sum 
Adding 
3 to sum 
Adding 
5 to sum 
Adding 
7 to sum 
Adding 
9 to sum 
Sum 
= 25

  
//再如另外一个例子
  
//Student 对象引用群集
  Arraylist studentBody = new ArrayList();
  
//细节从略
  foreach (Student s in stadentBody){
  
//s自动指向Student
  s.Compute GPA();
    }
  
//foreach 语句在每次循环当中都会从指定类型的元素集合中取出一个元素,并执行foreache语句所包含的代码块.
  
//注意,一旦进入一个foreach代码块马厩不能试图修改foreach引用变量的值;即,下面的
//代码无法被正常编译:
   foreach (Student s in studentBody){
    
//这将不能被编译,引用变量s是只读的.
    s = (Student) studentBody[0];
    }
  error CS1640:can not assign to 
's' because it is read-only
    
//当然,由S所指代的对象是可访问(accessible)切可修改(modifiable)的,情况与
//任何其他拥有引用对象一样:
    foreach(student s in studentBody){
    
//可被执行
     s.Name = "?";
    }

    下面上一最后一个关于foreach循环的例子,它使用foreach循环,遍历一个混杂存储了Professor对象和Student对象的ArrayList中的元素,假定Professor和Student类都是从Person类派生而来,我们将把它们的公共超类Person作为ArrayList中搜索的类型,ArrayList中的每个Person对象的名字将显示出来.

 

 

using System;
suing System.Collections;

public class FpreachDemo
{
 
static void Main(){
   
//创建一个ArrayList里面包含Student和Professor对象.
    ArrayList people = new ArrayList();
    people.Add(
new Professor("Jacquie Barker"));
    people.Add(
new Student("Maria vasquez"));
    people.Add(
new Professor("John Carson"));
    people.Add(
new Professor(""Mike Vito));
    people.Add(
new Student("Jackson Palmer"));
   
//使用foreach循环遍历ArrayList,得到每个Person对象引用,并打印他们的名字.
    foreach(Person p in people){
      Console.WriteLine(p.Name);
  }

 }

}

输出结果将会是:
Hacquie Barker
Maria Vasquez
Jon Carson Mike Vito 
Jackson Palmer
  

    当然,如果foreach语句中的类型与群集类中某些条目不匹配,在运行时的隐式类型转换竟导致抛出一个InvalidCastException异常,就象我们尝试做非法的显示转换一样