第十二章 遍历器

  遍历器(Iterator)的作用是按照指定的顺序来访问一个集合中的所有元素,而不需要了解集合的详细数据结构。

1 概述

1.1 foreach语句

  这种遍历方式对任何类型的数据都适用,因为所有数组都继承了.NET类库中定义的类System.Array,而该类继承了接口IEnmerable。在C#中,如果某个类型继承了接口IEnumerable,或者继承了泛型接口IEnumerable<T>,或者继承了泛型接口IEnumerable<T>的任何一个构造类型,那么称该类型是“可枚举的”。

  可枚举类型通常表示一个集合。除数组之外,.NET类库中定义的枚举类型的例子有:字符串类String,表示XML文件节点的类XmlNode,表示动态列表的泛型类List<T>等。这些类型都可以使用foreach循环来遍历集合中的每一个元素。

1.2 Iterator模式

  从很多事物中可以提取出许多共性的要素,而人们在设计过程中总是自觉不自觉地在使用这些要素,这些要素的抽象就是设计模式。

  在各种各样的类库的设计中,面向对象的设计模式得到了广泛的应用。.NET类库的设计模式中使用到了Iterator模式,它是Erich Gamma总结的面向对象的设计模式这一,遍历器的概念正是对这一模式的实现。

  Iterator模式的作用就是对集合中一系列元素进行访问,基本思想是:集合对象只负责维护集合中的各个元素,而对元素的访问则通过定义一个新的枚举器对象来进行;枚举器对象负责获取集合中的元素;并允许按照特定的顺序来遍历这些元素。与枚举器相关联的集合叫做可枚举集合。该模式的优点在于不需要暴露集合的内部结构,同时可以为一个集合实现多种遍历方式。

  在.NET类库中,抽象可枚举集合所对应的类型可以是IEnumerable和IEnumerable<T>;如果一个非抽象类型继承了其中的任何一个接口,该类型就是一个具体的可枚举集合。抽象枚举器所对应的类型可以是IEnumerator和IEnumerator<T>,它们提供的方法成员用于查询可枚举集合的状态以及访问集合中的元素;而它们的非抽象派生类型同样都可以作为具体的枚举器。为集合获取枚举器对象的方法就是IEnumerable和IEnumerable<T>的GetEnumerator方法。

2 使用可枚举类型

2.1 IEnumerable和IEnumerable<T>接口

  如果希望自己定义的集合对象也能够使用foreach循环,方法很简单,就是该类型继承接口IEnumerable或IEnumerable<T>。这两个接口都只定义了一个成员方法,其原型分别为:

IEnumerator GetEnumerator();
IEnumerator<T> GetEnumerator();

  从这两个接口中派生的可枚举类型就都需要实现这两个接口方法。

public class Assemble<T> : IEnumerable
{
    //字段
    protected T[] m_list;

    ...

    //遍历器方法
    public IEnumerator GetEnumerator()
    {
        for(int i=0;i<m_count;i++)
            yield return m_list[i];
        }
    }
}

  通过GetEnumerator方法就得到了可枚举类型Assemble<T>的一个枚举器,通过它能够遍历集合m_list中的每个对象。该方法就是一个遍历器,而方法的执行代码就叫做遍历器代码。代码中使用的yield return语句用于依次生成一系列对象,其中的每一个都可以在foreach循环语句中被遍历,且遍历顺序与yield return语句生成的顺序一致。

  yield return语句所生成对象的类型,必须与可枚举类型所管理的集合元素类型一致,或是可以隐式转换为这些元素的类型。如果可枚举类型继承的接口是IEnumerable,其元素类型就是object,这样yield return语句后面就可以是任何类型的表达式,但在foreach循环遍历时通常就要进行装箱和拆箱转换。而如果可枚举类型继承的是接口IEnumerable<T>或其某个构造类型,相应的元素类型就为T或其封闭类型。对于Assemble<T>这样的泛型类,更常见的情况是使其继承泛型接口IEnumerable<T>,这样就避免了类型转换的问题。

public class Assemble<T> : IEnumerable<T>
{
    //字段
    protected T[] m_list;

    ...

    //遍历器方法
    public IEnumerator<T> GetEnumerator()
    {
        for(int i=0;i<m_count;i++)
            yield return m_list[i];
        }
    }
}

2.2 实现多种遍历方式

  通过GetEnumerator方法所实现的遍历器叫做默认遍历器。C#还支持为同一个枚举类型实现多个遍历器,从而以不同的方式来遍历集合中的元素。非默认的遍历器也是通过方法成员来实现的,不同的是,它们要求方法成员的返回类型为IEnumerable或IEnumerable<T>。

public class Assemble<T> : IEnumerable<T>
{
    //字段
    protected T[] m_list;

    ...

    //遍历器方法
    public IEnumerator<T> GetEnumerator()
    {
        for(int i=0;i<m_count;i++)
            yield return m_list[i];
        }
    }

    public IEnumerable<T> GetReverseEnumerator()
    {
        for(int i=m_count-1;i>=0;i--)
            yield return m_list[i];
    }
}

  这样就可以实现正向和逆向地输出集合的所有元素。

  foreach语句中,关键字in后面的对象类型必须为可枚举类型。在可枚举类型定义代码中出现的this关键字所代表的对象显然也是可枚举的,因此可以将它作为非默认遍历器的返回值。例如,可以为泛型类Assemble<T>再增加下面的属性定义,而它所实现的遍历方式与默认遍历器相同:

public IEnumerable<T> ObverseEnumerator
{
    get
    {
        return this;
    }
}

  下面是一个完整的示例:

程序代码还在调试,稍后修正

2.3 带参遍历

  实现非默认遍历器方法还可以带有参数,通过传递参数来控制遍历的过程。

3 使用枚举器

4 遍历器工作机制

4.1 遍历器代码

4.2 遍历流程

5 示例程序:联系人分类输出

时间: 2024-12-12 20:35:22

第十二章 遍历器的相关文章

第十二章 类加载器和反射机制

12 类加载器和反射机制 12.1 类加载器 负责将.class文件加载到内存中,并为之生成对应的Class对象. 1.类的加载 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载.连接.初始化三个步骤来实现对这个类的初始化. 加载 就是指将calss文件读入到内存,并为之穿件一个Class对象. 任何类被使用时系统都会建立一个Class对象. 连接 验证    是否有正确的内部结构,并和其他类协调一致 准备    负责为类的静态成员分配内存,并设置默认初始化值 解析    将类

C和指针 (pointers on C)——第十二章:使用结构和指针

第十二章 使用结构和指针 这章就是链表.先单链表,后双向链表. 总结: 单链表是一种使用指针来存储值的数据结构.链表中的每个节点包含一个字段,用于指向链表的下一个节点. 有一个独立的根指针指向链表的第1个节点.单链表只能从一个方向遍历. 如何insert单链表:1.新节点的link字段必须设置为指向它的后面节点.2.前一个节点的link字段必须指向这个新节点. 为了防止可能会插入链表的起始位置这种情况,在C中,可以保存一个指向必须进行修改的link字段的指针,而不是保存一个指向前一个节点的指针.

C和指针 (pointers on C)——第十二章:利用结构和指针

第十二章 利用结构和指针 这章就是链表.先单链表,后双向链表. 总结: 单链表是一种使用指针来存储值的数据结构.链表中的每一个节点包括一个字段,用于指向链表的下一个节点. 有一个独立的根指针指向链表的第1个节点. 单链表仅仅能从一个方向遍历. 怎样insert单链表:1.新节点的link字段必须设置为指向它的后面节点. 2.前一个节点的link字段必须指向这个新节点. 为了防止可能会插入链表的起始位置这样的情况,在C中,能够保存一个指向必须进行改动的link字段的指针.而不是保存一个指向前一个节

算法导论(Introduction to Algorithms )— 第十二章 二叉搜索树— 12.1 什么是二叉搜索树

搜索树数据结构支持许多动态集合操作,如search(查找).minmum(最小元素).maxmum(最大元素).predecessor(前驱).successor(后继).insert(插入).delete(删除),这些都是基本操作,可以使用一颗搜索树当做一个字典或者一个优先队列. 12.1.什么事二叉搜索树 二叉搜索树是以一棵二叉树来组织的,可以用一个链表数据结构来表示,也叫二叉排序树.二叉查找树: 其中的每个结点都是一个对象,每个对象含有多个属性(key:该结点代表的值大小,卫星数据:不清楚

Gradle 1.12用户指南翻译——第二十二章. 标准的 Gradle 插件

其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Github上的地址: https://github.com/msdx/gradledoc/tree/1.12. 直接浏览双语版的文档请访问: http://gradledoc.qiniudn.com/1.12/userguide/userguide.html. 另外,Android 手机用户可通过我写的一个程序浏览文档,带缓存功能的,兼容

算法导论第十二章__二叉搜索数

package I第12章__二叉搜索树; //普通二叉树 public class BinaryTree<T> { // -----------------------数据结构--------------------------------- private int height = 0; private Node<T> rootNode; class Node<T> { T t; int key; Node left; Node right; public Node

第十二章——SQLServer统计信息(2)——非索引键上统计信息的影响

原文:第十二章--SQLServer统计信息(2)--非索引键上统计信息的影响 前言: 索引对性能方面总是扮演着一个重要的角色,实际上,查询优化器首先检查谓词上的统计信息,然后才决定用什么索引.一般情况下,默认会在创建索引时,索引列上均创建统计信息.但是不代表在非索引键上的统计信息对性能没有用. 如果表上的所有列都有索引,那么将会是数据库负担不起,同时也不是一个好想法,包括谓词中用到的所有列加索引同样也不是好方法.因为索引会带来负载.因为需要空间存放索引,且每个DML语句都会需要更新索引. 一般

第十二章——SQLServer统计信息(1)——创建和更新统计信息

原文:第十二章--SQLServer统计信息(1)--创建和更新统计信息 简介: 查询的统计信息: 目前为止,已经介绍了选择索引.维护索引.如果有合适的索引并实时更新统计信息,那么优化器会选择有用的索引供查询之用,因为SQLServer优化器是基于开销的优化.当在where和on上的列上的数据需要显示在结果集的时候,如果有实时的统计信息,优化器会选择最好的执行方式,因为优化器会从统计信息中获得这些数据的明细情况. 在创建索引的时候,SQLServer就会在索引列上创建统计信息.简单来说,统计信息

第十二章——SQLServer统计信息(3)——发现过期统计信息并处理

原文:第十二章--SQLServer统计信息(3)--发现过期统计信息并处理 前言: 统计信息是关于谓词中的数据分布的主要信息源,如果不知道具体的数据分布,优化器不能获得预估的数据集,从而不能统计需要返回的数据. 在创建列的统计信息后,在DML操作如insert.update.delete后,统计信息就会过时.因为这些操作更改了数据,影响了数据分布.此时需要更新统计信息. 在高活动的表中,统计信息可能几个小时就会过时.对于静态表,可能几个星期才会过时.这要视乎表上DML的操作. 从2000开始,