C#图解教程.docx

上传人:牧羊曲112 文档编号:3153654 上传时间:2023-03-11 格式:DOCX 页数:52 大小:52.98KB
返回 下载 相关 举报
C#图解教程.docx_第1页
第1页 / 共52页
C#图解教程.docx_第2页
第2页 / 共52页
C#图解教程.docx_第3页
第3页 / 共52页
C#图解教程.docx_第4页
第4页 / 共52页
C#图解教程.docx_第5页
第5页 / 共52页
亲,该文档总共52页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述

《C#图解教程.docx》由会员分享,可在线阅读,更多相关《C#图解教程.docx(52页珍藏版)》请在三一办公上搜索。

1、C#图解教程数 组 本章内容 . 数组 . 数组的类型 . 数组是对象 . 一维数组和矩形数组 . 实例化一维数组或矩形数组 . 访问数组元素 . 初始化数组 . 交错数组 . 比较矩形数组和交错数组 . foreach语句 . 数组协变 . 数组继承的有用成员 . 比较数组类型 14.1 数组 数组实际上是由一个变量名称表示的一组同类型的数据元素。每个元素通过变量名称和一个 或多个方括号中的索引名称来访问,如下所示: 14.1.1 定义 让我们从C#中与数组有关的一些重要定义开始。 . 元素:数组的独立数据项被称作元素。数组的所有元素必须是相同类型的,或继承自相 同的类型。 第 14 章 数

2、组名 索引 . 秩/维度:数组可以有任何为正数的维度数。数组的维度数称作秩。 . 维度长度:数组的每一个维度有一个长度,就是这个方向的位置个数。 . 数组长度:数组的所有维度中的元素的总和称为数组的长度。 14.1.2 重要细节 下面是有关C#数组的一些要点: . 数组一旦被创建,大小就固定了。C#不支持动态数组。 . 数组索引号是从0开始的。也就是说,如果维度长度是n,索引号范围是从0到n.1。例如, 图14-1演示了两个示例数组的维度和长度。注意,对于每一个维度,索引范围从0到长度.1。 图14-1 维度和大小 14.2 数组的类型 C#提供了两种类型的数组: . 一维数组可以认为是单行元

3、素或元素向量。 . 多维数组是由主向量中的位置组成的,每一个位置本身又是一个数组,称为子数组 。子数组向量中的位置本身又是一个子数组。 另外,有两种类型的多维度数组:矩形数组和交错数组, 它们有如下特性。 . 矩形数组 . 某个维度的所有子数组有相同长度的多维数组。 . 不管有多少维度,总是使用一组方括号。 . 交错数组 . 每一个子数组都是独立数组的多维度数组。 . 可以有不同长度的子数组。 . 为数组的每一个维度使用一对方括号。 一维数组 二维数组 秩=1 数组长度=5 秩=2 数组长度=18 一组方括号 三组方括号 图14-2演示了C#中的各种数组。 图14-2 一维、矩形以及交错数组

4、14.3 数组是对象 数组实例是从System.Array继承的对象。由于数组从BCL基类继承,它们也继承了很多有 用的方法,如下所示。 . Rank:返回数组维度数的属性。 . Length:返回数组长度的属性。 数组是引用类型,与所有引用类型一样,有数据的引用以及数据对象本身。引用在栈或堆上, 并且数组对象本身总是在堆上。图14-3演示了数组的内存配置和组成部分。 图14-3 数组的结构 尽管数组总是引用类型,但是数组的元素可以是值类型或引用类型。 . 如果存储的元素都是值类型,数组被称作值类型数组。 . 如果存储在数组中的元素都是引用类型对象,数组被称作引用类型数组。 图14-4演示了值

5、类型数组和引用类型数组。 一维 三维 交错数组 一维数组 矩形数组 交错数组 二维 从System.Array继 承的 属性或方法 内存 元素 堆 图14-4 元素可以是值引用 14.4 一维数组和矩形数组 一维数组和矩形数组的语法非常相似,因此我把它们放在了一起。然后,我会单独介绍交错 数组。 声明一维数组或矩形数组 要声明一维数组或矩形数组,可以在类型和变量名称之间使用一对方括号。 方括号内的逗号就是秩说明符,它们指定了数组的维度数。秩就是逗号数量加1。比如,没 有逗号代表一维数组,一个逗号代表二维数组,依此类推。 基类和秩说明符构成了数组类型。例如,如下代码行声明了long的一维数组。数

6、组类型是 long,读作“long数组”。 如下代码展示了矩形数组声明的示例。注意: . 可以使用任意多的秩说明符。 . 不能在数组类型区域中放数组维度长度。秩是数组类型的一部分,而维度长度不是类型 的一部分。 . 数组声明后,维度数就是固定的了。然而,维度长度直到数组实例化时才会被确定。 内存 堆 值 引用 秩说明符=1 数组类型 说明 和C/C+不同,方括号在基类型后,而不在变量名称后。 14.5 实例化一维数组或矩形数组 要实例化数组,我们可以使用数组创建表达式。数组创建表达式由new运算符构成,后面是 基类名称和一对方括号。方块号中以逗号分隔每一个维度的长度。 下面是一维数组声明的示例

7、: . arr2数组是四个int的一维数组。 . mcArr数组是4个MyClass引用的一维数组。 图14-5演示了它们在内存中的布局。 下面是矩形数组的示例,arr3数组是三维数组。 . 数组长度是3*6*2=36。 . 图14-5演示了它在内存中的布局。 秩说明符 不允许维度长度 数组类型 四个元素 数组创建表达式 维度长度 数组类型:三维整型数组 数组类型:二维整型数组 数组类型:三维long数组 编译错误 图14-5 声明和实例化数组 说明 与对象创建表达式不一样,数组创建表达式不包含圆括号即使是对于引用类型数 组。 14.6 访问数组元素 在数组中使用整型值作为索引来访问数组元素。

8、 . 每一个维度的索引从0开始。 . 方括号内的索引在数组名称之后。 如下代码给出了声明、写入、读取一维数组和二维数组的示例: 如下代码给出了一个创建并访问一维数组的完整过程: 这段代码产生了如下的输出: 堆 声明一维数组 向第3个元素写入值 从第3个元素读取值 声明二维数组 向数组写入值 向数组读取值 声明数组 实例化数组 设置值 读取并输出每个数组元素的值 14.7 初始化数组 当数据被创建之后,每一个元素被自动初始化为类型的默认值。对于预定义的类型,整型默 认值是0,浮点型的默认值为0.0,布尔型的默认值为false,而引用类型的默认值则是null。 例如,如下代码创建了数组并将它的4个

9、元素的值初始化为0。图14-6演示了内存中的布局。 图14-6 一维数组的自动初始化 14.7.1 显式初始化一维数组 对于一维数组,我们可以通过在数组实例化的数组创建表达式之后包括初始化列表来设置显 式初始值。 . 初始值必须以逗号分隔,并封闭在一组花括号内。 . 维度长度是可选的,因为编译器可以通过初始化值的个数来推断长度。 . 注意,在数组创建表达式和初始化列表中间没有分隔符。也就是说,没有等号或其他连 接运算符。 例如,下面的代码创建了一个数组,并将它的4个元素初始化为花括号内的值。图14-7演示 了内存中的布局。 图14-7 一维数组的显式初始化 内存 内存 没有连接运算符 初始化列

10、表 14.7.2 显式初始化矩形数组 要显式初始化矩形数组: . 每一个初始值向量必须封闭在花括号内。 . 除了初始值,每一个维度的初始化列表和组成部分必须使用逗号分隔。 例如,如下代码演示了具有初始化列表的二维数组的声明。图14-8演示了在内存中的布局。 图14-8 初始化矩形数组 14.7.3 初始化矩形数组的语法点 矩形数组使用嵌套的、逗号分隔的初始化列表进行初始化。初始化列表嵌套在花括号内。有 时会混淆,因此,对于嵌套、分组和逗号的正确使用,如下技巧可能会有用: . 逗号用作元素和分组之间的分隔符。 . 逗号不在左花括号之间使用。 . 逗号不在右花括号之前使用。 . 从左向右读佚指示符

11、,指定最后一个数字作为“元素”,其他数字作为“分组”。 例如,下面的声明可以读作:“intArray有四组两个元素一组的分组三组。” 14.7.4 快捷语法 在语句中结合声明、数组创建和初始化时,我们可以省略语法的数组创建表达式部分。快捷 语法如图14-9所示。 初始化列表由逗号分隔 由逗号分隔的嵌套初始化列表 内存 图14-9 声明、创建以及初始化数组的快捷语法 14.7.5 隐式类型数组 直到现在,我们一直都在数组声明的开始处显式指定数组类型。然而,在C# 3.0中,和其他 局部变量一样,数组可以是隐式类型的。也就是说: . 当初始化数组时,我们可以让编译器根据初始化器的类型来推断数组类型

12、。只要所有初 始化器能隐式转换为单个类型,就可以这么做。 . 和隐式类型的局部变量一样,使用var关键字来替代数组类型。 如下代码演示了三组数组的声明的显式版本和隐式版本。第一组是一维int数组。第二组是 二维int数组。第三组是字符串数组。注意,在隐式类型intArr4的声明中,我们仍然需要在初 始化中提供佚说明符。 14.7.6 综合内容 如下代码把我们迄今学到的知识点放在了一起。它创建、初始化并使用了一个矩形数组。 这段代码产生了如下的输出: 等价 等价 显式 显式 关键字 推断 秩说明符 声明、创建和初始化一个隐式类型的数组 输出值 14.8 交错数组 交错数组是数组的数组。与矩形数组

13、不同,交错数组的子数组可以有不同数目的元素。 例如,如下代码声明了一个二维交错数组。图14-10演示了数组在内存中的布局。 . 第一个维度的长度是3。 . 声明可以读作“jagArr是三个int数组的数组”。 . 注意,图中有四个数组对象其中一个针对顶层数组,另外三个针对子数组。 图14-10 交错数组是数组的数组 14.8.1 声明交错数组 交错数组的声明语法要求每一个维度都有一对独立的方括号。数组变量声明中的方括号数决 定了数组的佚。 . 交错数组可以有任何大于1的维度。 . 和矩形数组一样,维度长度不能包括在数组类型的声明部分中。 jagArr3是具有三个数组的数 秩说明符 声明并创建顶

14、层数组 声明并创建子数组 内存 堆 秩等于2 14.8.2 快捷实例化 我们可以将用数组创建表达式创建的最高级别数组和交错数组的声明相结合,如下面的声明 所示。结果如图14-11所示。 图14-11 快捷最高级别实例化 不能在声明语句中初始化最高级别数组之外的数组。 14.8.3 实例化交错数组 和其他类型的数组不一样,交错数组的完全初始化不能在一个步骤中完成。由于交错数组是 独立数组的数组每一个数组必须独立创建。实例化完整的交错数组需要如下步骤: (1) 首先,实例化顶层数组。 (2) 其次,分别实例化每一个子数组,把新建数组的引用赋值给包含它们的数组的合适元 素。 例如,如下代码演示了二维

15、交错数组的声明、实例化和初始化。注意,在代码中,每一个子 数组的引用都赋值给了顶层数组的元素。步骤1到步骤4与图14-12中被编号的表示相对应。 数组类型 数组名 三个子数组 允许 不允许 内存 堆 秩等于3 编译错误 实例化顶层数组 实例化子数组 实例化子数组 实例化子数组 图14-12 创建一个二维交错数组 14.8.4 交错数组中的子数组 由于交错数组中的子数组本身就是数组,因此交错数组中也可能有矩形数组。例如,如下代 码创建了一个有三个二维矩形数组的交错数组,并将它们初始化,然后显示了它们的值。 图14-13演示了结构。 代码使用了数组的继承自System.Array的GetLengt

16、h(int n)方法来获取数组中指定维 度的长度。 内存 内存 堆 堆 堆 堆 获取Arr维度0的长度 获取Arri维度0的长度 获取Arri维度1的长度 带有二维数组的交错数组 实例化带有三个二维数组的交错数组 图14-13 三个二维数组构成的交错数组 14.9 比较矩形数组和交错数组 矩形数组和交错数组的结构的区别非常大。例如,图14-14演示了33的矩形数组以及一个 由三个长度为3的一维数组构成的交错数组的结构。 . 两个数组都保存了9个整数,但是它们的结构却很不相同。 . 矩形数组只有单个数组对象,而交错数组有4个数组对象。 图14-14 比较矩形数组和交错数组的结构 在CIL中,一维

17、数组有特定的指令用于性能优化。矩形数组没有这些指令,并且不在相同级 别进行优化。因此,有时使用一维数组的交错数组相比矩形数组会更有效率。 另一方面,矩形数组的编程复杂度更小,因为它会被作为一个单元而不是数组的数组。 14.10 foreach语句 foreach语句允许我们连续访问数组中的每一个元素。其实它是一个比较普遍的结构,因为可 以和其他集合类型一起使用但是在这部分内容中,我们只会讨论它和数组的使用,第20章会 介绍它和其他集合类型的使用。 有关foreach的重点如下所示。 堆 堆 33矩形数组 一个数组对象 没有优化 4个数组对象 更复杂 优化后的数组 33交错数组 . 迭代变量是临

18、时的,只读的,并且和数组中元素的类型相同。foreach语句使用迭代变 量来连续表示数组中的每一个元素。 . foreach语句的语法如下,其中: . Type是数组中元素的类型。我们可以显式提供它的类型,或者,从C# 3.0开始,也可以 隐式提供它的类型并通过编译器来推断,因为编译器知道数组的类型。 . 标识符是迭代变量的名字。 . 数组名称是要处理的数组的名字。 . 语句是要为数组中的每一个元素执行一次的单条语句或语句块。 在之后的内容中,有时会使用隐式类型,而有时又会使用显式类型,这样我们就可以看到使 用的确切类型,但是两种形式的语法是等价的。 foreach语句以如下方式工作: . 从

19、数组的第一个元素开始并把它赋值给迭代变量。 . 它然后执行语句主体。在主体中,我们可以把迭代变量作为数组元素的只读别名。 . 在主体执行之后,foreach语句选择数组中的下一个元素并重复处理。 这样,它就循环遍历了数组,允许我们逐个访问每一个元素。例如,如下代码演示了foreach 语句和一个具有4个整数的一维数组的使用: . foreach语句的主体WriteLine为数组的每一个元素执行一次。 . 第一次遍历时,迭代变量item就有数组的第一个元素的值。每次成功执行后,它就有了 数组中下一个元素的值。 14.10.1 迭代变量是只读的 由于迭代变量的值是只读的,所以它不能被改变。但是,对

20、于值类型数组和引用类型数组而 言效果不一样。 对于值类型数组,我们不能改变数组的数据。例如,在如下的代码中,尝试改变迭代变量中 显示类型迭代变量声明 隐式类型迭代变量声明 迭代变量声明 使用迭代变量 的数据产生了编译时错误消息: 对于引用类型,我们仍然不能改变迭代变量,但是迭代变量只是保存了数据的引用,而不是 数据本身。因此,我们可以通过迭代变量改变数据。 如下代码创建了一个有4个MyClass对象的数组并将其初始化。在第一个foreach语句中, 每一个对象中的数据被改变。在第二个foreach语句中,从对象读取改变后的值。 这段代码产生了如下的输出: 14.10.2 foreach语句和多

21、维数组 在多维数组中,元素的处理次序是最右边的索引号最先递增。当索引从0到长度减1时,下一 个左边的索引被递增,右边的索引被重置成0。 1矩形数组的示例 如下代码演示了foreach语句用于矩形数组: 创建数组 创建类对象 设置字段 改变数据 读取改变的数据 这段代码产生了如下的输出: 2交错数组的示例 一个交错数组是数组的数组,我们必须为交错数组中的每一个维度使用独立的foreach语 句。foreach语句必须嵌套以确保每一个嵌套数组都被正确处理。 例如,在如下代码中,第一个foreach语句遍历了顶层数组选择了下一个要处理 的子数组。内部的foreach语句处理了子数组的每一个元素。 这

22、段代码产生了如下的输出: 14.11 数组协变 在某些情况下,即使某个对象不是数组的基类型,我们也可以把它赋值给数组元素。这种属 性叫做协变。在下面的情况下可以使用协变: . 数组是引用类型数组。 . 在赋值的对象类型和数组基类型之间有隐式转换或显式转换。 由于在派生类和基类之间总是有隐式转换的,因此总是可以将一个派生类的对象赋值给为基 类声明的数组。 例如,如下代码声明了两个类,A和B,B类从继承自A类。最后一行展示了把类型B的对象赋 值给类型A的数组元素而产生的协变。 图14-15演示了代码的内存布局。 图14-15 数组出现协变 A类型的两个数 组 声明的A类型的对象 B类型对象也可以被

23、接 受,因为它从A继承 堆 处理顶层数组 处理第二层数组 说明 值类型数组没有协变。 14.12 数组继承的有用成员 我之前提到过,C#数组从System.Array类继承。它们可以从基类继承很多有用的属性和方 法,表14-1列出了其中最有用的一些。 表14-1 数组继承的一些有用成员 成 员 类 型 生 存 期 意 义 Rank 属性 实例 获取数组的维度数 Length 属性 实例 获取数组中所有维度的元素总和 GetLength 方法 实例 返回数组的指定维度的长度 Clear 方法 静态 设置元素的范围为0或null Sort 方法 静态 在一维数组中对元素进行排序 BinarySea

24、rch 方法 静态 使用二进制搜索,搜索一维数组中的值 Clone 方法 实例 进行数组的浅复制复制值类型数组和引用类型数组的元素 IndexOf 方法 静态 返回一维数组中遇到的第一个值 Reverse 方法 静态 将一维数组中的某一范围内的元素顺序倒过来 GerUpperBound 方法 实例 获取指定维度的上限 例如,下面的代码使用了其中的一些属性和方法: 基类 派生类 两个A类型的数组 普通:将A类型的对象赋值给A类型的数组 协变:将B类型的对象赋值给A类型的数组 这段代码产生了如下的输出: Clone方法 Clone方法为数组进行浅复制。也就是说,它只创建了数组本身的克隆。如果是引用

25、类型数组, 它不会复制元素引用的对象。对于值类型数组和引用类型数组而言,有不同的结果。 . 克隆值类型数组会产生两个独立数组。 . 克隆引用类型数组会产生指向相同对象的两个数组。 Clone方法返回object类型的引用,它必须被强制转换成数组类型。 例如,如下代码给出了一个克隆值类型数组的示例,它产生了两个独立的数组。图14-16演 示了代码中的一些步骤。 数组类型 返回 bj t 图14-16 克隆值类型数组产生了两个独立数组 克隆引用类型数组会产生指向相同对象的两个数组,如下代码给出了这个示例。图14-17演 示了代码中的一些步骤。 图14-17 克隆引用类型数组产生了引用相同对象的两个

26、数组 堆 堆 堆 堆 堆 步骤1 步骤2 步骤3 14.13 比较数组类型 表14-2总结了三种类型的数组的重要相似点和不同点。 表14-2 比较数组类型的总结 数 组 类 型 数 组 对 象 语 法 形 状 结 构 逗 号 一维 1 单组 没有 一维int3 . 在CIL中优化指令 矩形 . 多维度 1 单组 有 二维int3,6 . 多维数组中的子数组 必须是相同长度的 三 维 int3,6,2 交错 . 多维度 . 子数组可以有不同长 度 多个 多组 没有 交错int4 步骤1 步骤2 步骤3 枚举数和迭代器 本章内容 . 枚举数和可枚举类型 . 使用IEnumerator接口 . IE

27、numerable接口 . 不实现接口的枚举数 . 泛型枚举接口 . IEnumerator<T>接口 . IEnumerable<T>接口 . 迭代器 . 常见迭代器模式 . 产生可枚举类型和枚举数 . 产生多个可枚举类型 . 产生多个枚举数 . 迭代器实质 20.1 枚举数和可枚举类型 在第14章中,我们已经知道可以使用foreach语句来遍历数组中的元素。在本章中,我们会 进一步探讨数组,来看看为什么它们可以被foreach语句处理。我们还会研究如何为用户自定义 的类增加这个功能。在本章的后面部分,我们会讨论迭代器的使用。 20.1.1 使用 foreach 语句 当我们

28、为数组使用foreach语句时,这个语句为我们依次取出了数组中的每一个元素,允许 我们读取它的值。 例如,如下的代码声明了一个有4个元素的数组,然后使用foreach来循环打印这些项的值: 第 20 章 这段代码产生了如下的输出: 为什么数组可以这么做?看上去很神奇。原因是数组可以按需提供一个叫做枚举数的对象。枚举数可以依次返回请求的数组的元素。枚举数“知道”项的次序并且跟踪它 在序列中的位置,然后返回请求的当前项。 对于有枚举数的类型而言,必须有一个方法来获取它们。在.NET中获取一个对象枚举数的 标准方法是调用对象的GetEnumerator方法。实现GetEnumerator方法的类型叫

29、做可枚举类型 。数组是可枚举类型。 图20-1演示了可枚举类型和枚举数之间的关系。 图20-1 枚举数和可枚举类型概览 foreach结构被设计用来和可枚举类型一起使用。只要给它的遍历对象是可枚举类型,比如数 组,它就会执行如下行为: . 通过调用GetEnumerator方法获取对象的枚举数。 . 从枚举数中请求每一项并且把它作为迭代变量,代码可以读但不可以改变。 20.1.2 枚举数类型 枚举数一共有三种。它们的工作原理本质上是一样的,只是有一些细小的区别。我会讨论每 一种类型。我们可以使用以下方式实现枚举数: . IEnumerator/IEnumerable接口叫做非泛型接口形式。 可

30、枚举类型是带有GetEnumerator 方法的类型,它返回用于项的枚举数 枚举数是可以依次返 回集合中项的类对象 GetEnumerator方法返回 枚举数的实例 可枚举类型 枚举数 位置 项 必须是可枚举类型 . IEnumerator<T>/IEnumerable<T>接口叫做泛型接口形式。 . 不使用接口形式。 20.2 使用 IEnumerator 接口 这部分内容会先来介绍前面列表中的非泛型接口形式。这种形式的枚举数是实现 IEnumerator接口的类。之所以叫做非泛型是因为它没有使用C#泛型。 IEnumerator接口包含三个函数成员:Current、Move

31、Next以及Reset。 . Current返回序列中当前位置项的属性。 . 它是只读属性。 . 它返回object类型的引用,所以可以返回任何类型。 . MoveNext是把枚举数位置前进到集合中下一项的方法。它也返回布尔值,指示新的位置 是有效位置或已经超过了序列的尾部。 . 如果新的位置是有效的,方法返回true。 . 如果新的位置是无效的,方法返回false。 . 枚举数的原始位置在序列中的第一项之前。MoveNext必须在第一次使用Current之前 使用,否则CLR会抛出一个InvalidOperationException异常。 . Reset方法把位置重置为原始状态。 图20-

32、2左部显示了三个项的集合,在右部显示了枚举数。在图20-2中,枚举数是一个叫做 ArrEnumerator类的实例。 图20-2 小集合的枚举数 枚举数类通常被声明为类中的嵌套类。嵌套类是声明在另外一个类声明中的类,第25章描述 了嵌套类的一些细节。 枚举数与序列中的当前项保持联系的方式完全取决于实现。可以通过对象引用、索引值或其 他方式来实现。对于数组来说,就使用项的索引。 图20-3演示了有三个项的集合的枚举数的状态。这些状态标记了1到5。 . 注意,在状态1中,枚举数的原始位置是-1。 . 状态的每次切换都由MoveNext进行,它提升了序列中的位置。每次调用MoveNext时, 状态1

33、到状态4都返回true,然而,在从状态4到状态5的过程中,位置最终超过了集合的 最后一项,所以方法返回false。 三个项的集合 IEnumerator的成员 当前项的位置 Current返回当前位 置的项 MoveNext 前 进 位 置 到下一项 Reset把位置设置回 原始配置 图20-3 枚举数状态 . 在最后一个状态中,任何进一步的调用MoveNext总是会返回false。 有了集合的枚举数,我们就可以使用MoveNext和Current成员来模仿foreach循环遍历集 合中的项。我们已经知道了,例如数组就是可枚举类型,所以下面的代码手动做foreach语句自 动做的事情。输出和使

34、用foreach循环的输出一样。 这段代码产生了如下的输出: 声明 IEnumerator 的枚举数 要创建非泛型接口的枚举数类,我们必须声明实现IEnumerator接口的类。IEnumerator接 口有如下的特性: 获取枚举数 移到下一项 获取当前项 输出 . 它是System.Collection命名空间的成员。 . 它包含三个方法Current、MoveNext和Reset。 如下代码演示了非泛型枚举数类的框架。这里没有显示是如何保持位置的。注意,Current 返回object的引用。 例如,下面代码实现了一个列出颜色名数组的枚举数类: 返回object的引 实现IEnumerat

35、or 包含命名空间 构造函数 20.3 IEnumerable 接口 IEnumerable接口只有一个成员Get- Enumerator方法,它返回对象的枚举数。 图20-4演示了一个有三个要枚举项的类 MyClass,通过实现GetEnumerator方法 来实现IEnumerable接口。 如下代码演示了可枚举类的声明形式: 如下的代码给出了一个使用之前示例中的ColorEnumerator枚举数类的示例。要知道, ColorEnumerator实现了IEnumerator。 使用 IEnumerable 和 IEnumerator 的示例 把MyColors和ColorEnumerat

36、or示例放在一起,我们可以添加一个叫做Program的类,其 中有一个Main方法用来创建MyColors的实例,并在foreach中使用。 实现IEnumerable接 返回IEnumerator类型的对 象 枚举数类的实例 图20-4 GetEnumerator方法返回类的一个枚举数对 构造函数 这段代码产生了如下的输出: 20.4 不实现接口的枚举数 我们已经知道如何使用IEnumerable和IEnumerator接口来创建可枚举类型和枚举数,但 是这种方法有几个缺点。 首先,由Current返回的对象是object类型的。对于值类型而言,在由Current返回之前 必须装箱成obje

37、ct。在从Current获取之后,又必须再一次拆箱。如果需要操作大量的数据, 会带来严重的性能问题。 非泛型接口方法的另外一个缺点是失去了类型安全。值被作为对象来枚举,所以可以是任何 类型。这就消除了编译时的类型检测。 我们可以通过对枚举数和可枚举类型类的声明做如下改变来解决这个问题。 . 对于枚举数类: . 不要继承自IEnumerator。 . 像以前一样实现MoveNext。 . 像以前一样实现Current,把返回类型设置为和枚举的项一样。 . 不需要实现Reset。 . 对于可枚举类: . 不要继承自IEnumerable。 . 像以前一样实现GetEnumerator,设置返回值为

38、枚举数类。 图20-5展示了它们的区别。左边是非泛型接口代码,右边是非接口代码。有了这样的修改, foreach语句可以完美处理集合而且还没有前面列出的一些缺点。 图20-5 比较基于接口的和非基于接口的枚举数 对于非接口的枚举数的实现来说,一个可能的问题就是其他程序集的类型可能希望使 用接口方法来实现枚举。如果这些类型尝试使用接口转换来获取类对象的枚举,可能就会 找不到。 要解决这个问题,我们可以在同一个类中实现两种形式。也就是说,在类级别创建 Current、MoveNext、Reset和GetEnumerator的实现,并且也为它们创建显式接口实现。 有了这两组实现,foreach和其他

39、结构会调用类型安全的、有效的实现,而其他结构可以调 用显式接口实现。 20.5 泛型枚举接口 第三种形式的枚举数是使用泛型接口IEnumerable<T>和IEnumerator<T>。它们被叫做泛 型是因为使用了C#泛型,使用方式和非泛型形式差不多。当然,这两者之间的差别如下所示。 . 对于非泛型接口形式: . IEnumerable接口的GetEnumerator方法返回实现IEnumerator枚举数类的实例。 . 实现IEnumerator的类实现了Current属性,它返回object的引用,然后我们必须把它 转化为实际类型的对象。 . 对于泛型接口形式: . IEn

40、umerable<T>接口的GetEnumerator方法返回实现IEnumator<T>的枚举数类的 实例。 . 实现IEnumerator<T>的类实现了Current属性,它返回实际类型的对象,而不是 object基类的引用。 需要重点注意的是,非泛型接口的实现不是类型安全的。它们返回object类型的引用,然 后需要转化为实际类型,而泛型接口的枚举数是类型安全的,它返回实际类型的引用。 20.6 IEnumerator<T>接口 IEnumerator<T>接口使用泛型来返回实际的类型,而不是object类型的对象。 IEnumerator<

41、T>接口从另外两个接口继承非泛型IEnumerable接口和IDisposable接 口。所以,它肯定实现了它们的成员。 . 我们已经知道了非泛型接口IEnumerable和它的三个成员。 . IDisposable接口只有一个叫做Dispose的类型为void的无参方法,它可以用于释放由 类占据的非托管资源。 . IEnumerator<T>接口本身只有一个Current方法,它返回衍生类型的项不是 object类型的项。 . 由于IEnumerator<T>和IEnumerator都有一个叫做Current的成员,我们应该显式实 现IEnumerator版本,然后在类中

42、实现泛型版本,如图20-6所示。 图20-6演示了接口的实现。 实现接口的类的声明和下面的代码差不多,T是有枚举数返回的类型。 图20-6 实现IEnumerator<T>接口 例如,如下代码使用泛型枚举数接口来实现ColorEnumerator实例: 这个版本的Current返 回 一 个 类 型 并 实 现 IEnumerator<T> 这些方法实现 IEnumerator 这个方法实现 IDisposable 这个版本的Current返回 object,显式实现Ienumerator的成员 显式实现 IEnumerator<T> Current IEnumerat

43、or Current IEnumerator MoveNext IEnumerator Reset IDisposable Dispose 20.7 IEnumerable<T>接口 泛 型 IEnumerable<T> 接 口 与 IEnumerable 的 非 泛 型 版 本 很 相 似 。 泛 型 版 本 从 IEnumerable继承,所以也必须实现IEnumerable接口。 返回衍生类型 显式实现 Current 泛型 Current 非泛型 构造函数 . 与IEnumerable差不多,泛型版本也包含了一个方法GetEnumerator。然而,这个 版本的GetE

44、numeratorfanui实现泛型IEnumerator<T>接口的类对象。 . 由于类必须实现两个GetEnumerator方法,我们需要显式实现非泛型版本,并在类中实 现泛型版本,如图20-7所示。 图20-7演示了接口的实现 图20-7 实现IEnumerable<T>接口 如下代码给出了实现泛型接口的模式。T是由枚举数返回的类型。 例如,如下代码演示了泛型可枚举接口的使用: 这个版本的GetEnumerator 实 现 IEnumerable<T> 并 返 回一个IEnumerator<T> 这 个 版 本 的 GetEnumerator IEnumerable并返回一个 IEnumerator。显式实现 为IEnumerable的成员 显式实现 版本 版本 20.8 迭代器 可枚举类型类和枚举数在.NET集合类中被广泛使用,所以知道它们如何工作很重要。但是, 既然我们已经知道如何创建自己的可枚举类以及枚举数了,我们可能会很高兴听到,C#从2.0版 本开始

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 生活休闲 > 在线阅读


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号