枚举和迭代器

Enumeration 和 Iterators

先来说一下 Enumerator 枚举器

枚举器是一个只读的,作用于一序列值的,只能向前的游标。

枚举器是一个实现了下列任意接口的对象:

  • System.Collections.IEnumerator
  • System.Collections.Generic.IEnumerator<T>

从技术上来说,任何一个含有名为MoveNext 方法和名为 Current 的属性对象,都会被当作枚举器来对待。

foreach 语句会迭代可枚举的对象(enumerable object)。可枚举的对象是一序列值的逻辑表示。它本身不是游标,它是一个可以基于本身产生游标的对象。

注意:枚举器和枚举类型没什么关系,下面是枚举类型的解释。

  • 枚举类型定义了一组“符号名称 / 值” 配对
  • 枚举是值类型,从System.Enum 派生,后者从System.ValueType 派生。
  • 枚举不能像值类型那样定义方法、属性、事件,但可利用C#的扩展方法功能模拟向枚举类型添加方法。
    internal static class FileAttributesExtensionMethods {    public static FileAttributes Set(this FileAttributes flags, FileAttributes setFlags) {        return flags | setFlags;    }    public static FileAttributes Clear(this FileAttributes flags,    FileAttributes clearFlags) {        return flags & ~clearFlags;    }    public static void ForEach(this FileAttributes flags,    Action<FileAttributes> processFlag) {        if (processFlag == null) throw new ArgumentNullException("processFlag");        for (UInt32 bit = 1; bit != 0; bit <<= 1) {            UInt32 temp = ((UInt32)flags) & bit;            if (temp != 0) processFlag((FileAttributes)temp);        }    }}

    下面是调用方式,看起来就像是枚举有了方法一样。

    FileAttributes fa = FileAttributes.System;fa = fa.Set(FileAttributes.ReadOnly);fa = fa.Clear(FileAttributes.System);fa.ForEach(f => Console.WriteLine(f))

下面介绍可枚举对象 enumerable object

一个可枚举对象可以是(下列任意一个):

  • 实现了IEnumerable 或者 IEnumerable<T> 的对象。
  • 有一个名为GetIEnumerator 的方法,并且该方法返回一个枚举器(enumerator)。

IEnumerator 和 IEnumerable 是定义在System.Collections 命名空间下的。

IEnumerator<T> 和 IEnumerable<T> 是定义在System.Collections.Generic 命名空间下的。

枚举模式 enumeration pattern

class Enumerator {    public IteratorVariableType Current {get {...}}    public bool MoveNext() {...} }class Enumerable {    public Enumerator GetEnumerator() {...}}

看一个例子:

foreach (char c in "beer")    Console.WriteLine(c);

using (var enumerator = "beer".GetEnumerator()){    while(enumerator.MoveNext())    {        var element = enumerator.Current;        Console.WriteLine(element);    }}

注意:如果枚举器(enumerator)实现了IDisposable 接口,那么foreach 语句就会像using 语句那样,隐式的dispose 掉这个 enumerator对象。

集合初始化器

你可以只用一步就把可枚举对象进行实例化并且填充里面的元素:

using System.Collections.Generic:...List<int> list = new List<int> {1,2,4};

但是C# 编译器会把它翻译成:

using System.Collections.Generic;...List<int> list = new List<int>();list.add(1);list.add(2);list.add(4);

凡是含有索引器的集合都可以这样写。

迭代器 Iterators

迭代器是含有一个或多个yield 语句的方法,属性或索引器。

迭代器必须返回下面四个接口中的一个(否则编译器会报错):

  • //Enumerable interfoces

    • System.Collections.IEnumerable<T>
    • System.Collections.Generic.IEnumerable<T>
  • //Enumerator interfaces
    • System.Collections.IEnumerator
    • System.Collections.IEnumerator<T>

根据迭代器返回的是 enumerable 接口还是enumerator 接口,迭代器会有不同的语义。

foreach 语句是枚举器(enumerate)的消费者,而迭代器是枚举器的生产者。看一个例子:

using System;using System.Collections.Generic;calss Test{    static void Main()    {        foreach(int fib in Fibs(6))            Console.Write(fib + " ");    }

    static IEnumerable<int> Fibs(int fibCount)    {        for(int i=0, prevFib =1, curFib=1; i<fibCount;i++)        {            yield return prevFib;            int newFib = prevFib + curFib;            prevFib = curFib;            curFib = newFib;        }    }}

迭代器的原理:

  • 编译器把迭代方法转换成私有的,实现了IEnumerable<T> 和/或 IEnumerator<T> 的类
  • 迭代器块内部的逻辑被反转并且被切分到编译器生成的枚举器类里面的MoveNext 方法和 Current 属性里。
  • 这意味着当你调用迭代器方法时,你所做的实际就是编译器生成的类进行实例化。
  • 你写的代码仅会在对结果序列进行枚举的时候才会运行,例如使用foreach 语句。

yield break

yield break 语句表示迭代器块会提前退出,不再返回更多的元素。

static IEnumerable<string> Foo (bool breakEarly){    yield return "One";    yield return "Two";    if(breakeEarly)        yield break;        yield return "Three"; //非法}

return 语句在迭代器块里面是非法的,你必须使用yield break 代替。

迭代器和 try/catch/finally 块

yield return 语句不可以出现含有在 catch 子句的try 块里面:

IEnumerable<string> Foo(){    try {yield return "One";} //非法的    catch {...} //在catch 也是非法的}//yield return 也不能出现在catch 或者finally 块里面。

但是 yield return 可以出现在只含有finally 块的try 块里面:

IEnumerable<string> Foo(){    try {yield return "One";} //合法    finally {...} }
  • 当消费的枚举器到达序列终点时或被disposed 的时候,finally 块里面的代码会执行。
  • 如果你提前进行了break,那么foreach 语句也会dispose 掉枚举器,所以用起来和安全。
  • 当显示使用枚举器的时候,通常会范这样一个错误:没有dispose 掉枚举器就不再用它了,这就绕开了finally 块。针对这种情况,你可以使用using 语句来规避风险:
    string firstElement =null;var sequence = Foo();using (var enumerator = sequence.GetEnumerator())    if(enumerator.MoveNext())        firstElement = enumerator.Current;

原文地址:https://www.cnblogs.com/mingjie-c/p/11680253.html

时间: 2024-11-25 03:53:58

枚举和迭代器的相关文章

十六、C# 常用集合类及构建自定义集合(使用迭代器)

常用集合类及构建自定义集合 1.更多集合接口:IList<T>.IDictionary<TKey,TValue>.IComparable<T>.ICollection<T> 2.主要集合类:List<T>.IDictionary<TKey,TValue>.SortedDictionary<TKey,TValue>和SortedList<T> Stack<T>.Queue<T>.Linke

6、iOS快速枚举

今天在写程序的时候想在当前视图跳转的时候释放掉当前视图上面add的一些子视图.因为add的子视图有些是在别的类里面add进来的,当前页面不知道自己当前有哪几个类型的子视图.这样,我就想到了用循环遍历来查看当前视图有没有符合条件的子视图,如果有的话就释放掉. 我是这样写的: for(UIView * subView in self.view.subviews) { if([subView isKindOfClass:[XYZSeniorQueryView class]]) { [subView r

Python 的枚举 Enum

枚举是常用的功能,看看Python的枚举. from enum import Enum Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')) 枚举的定义 首先,定义枚举要导入enum模块. 枚举定义用class关键字,继承Enum类. 注意: 定义枚举时,成员名称不允许重复 默认情况下,不同的成员值允许相同.但是两个相同值的成员,第二个

ios之快速枚举

for(UIView * subView in self.view.subviews) { if([subView isKindOfClass:[XYZSeniorQueryView class]]) { [subView removeFromSuperview]; subView = nil;//错误 } } 错误提示信息: Fast enumeration variables can't be modified in ARC by default;declare the variable _

Python 枚举

1. 枚举的定义 首先,定义枚举要导入enum模块.枚举定义用class关键字,继承Enum类.用于定义枚举的class和定义类的class是有区别. 示例代码: from enum import Enum class Color(Enum): red = 1 orange = 2 yellow = 3 green = 4 blue = 5 indigo = 6 purple = 7 代码分析: 上面的代码,我们定义了颜色的枚举Color. 颜色枚举有7个成员,分别是Color.red.Colo

泛型的几种类型以及初识webform

今天学习的可以分为两类吧,但是学习的都是比较抽象的,不太容易掌握吧.首先我们大部分时间学习了泛型,泛型的委托,泛型接口以及枚举器,迭代器,扩展方法:最后简单的认识了webform,实现了一个简单的功能. 一.泛型 定义:泛型(generic)可以软糖多个类型共享一组代码,泛型允许我们声明类型参数化.可以用不同的类型进行实例化,说白了,就是可以用类型占位符,创建具体类型致命的真实概念.C#中提供了五种泛型,类,结构,接口,委托和方法.下面举例说明可能更容易理解, class MyStack<T>

《Head First 设计模式》之适配器模式与外观模式

适配器模式(Adapter) 适配器(adapter-pattern):将一个类的接口,转换成客户期望的另一个接口.适配器让原来接口不兼容的类可以合作无间.两种形式: 对象适配器(组合) 类适配器(多重继承):在Java中不能实现 外观(facade-pattern):提供了一个统一的接口,用来访问子系统中的一群接口.外观定义了一个高层接口,让子系统更容易使用. 原则 最少知识原则:只和你的密友谈话 要点: 当需要使用一个现有的类而其接口不符合需要时,使用适配器.适配器改变接口以符合客户期望.

5. IO流:★★★★★

IO流:★★★★★,用于处理设备上数据. 流:可以理解数据的流动,就是一个数据流.IO流最终要以对象来体现,对象都存在IO包中. 流也进行分类: 1:输入流(读)和输出流(写). 2:因为处理的数据不同,分为字节流和字符流. 字节流:处理字节数据的流对象.设备上的数据无论是图片或者dvd,文字,它们都以二进制存储的.二进制的最终都是以一个8位为数据单元进行体现,所以计算机中的最小数据单元就是字节.意味着,字节流可以处理设备上的所有数据,所以字节流一样可以处理字符数据. 那么为什么要有字符流呢?因

【DAY11】关于集合的学习笔记

成员变量 === 属性  === 字段 === Field 成员函数 === 方法  === Method 构造函数 === 构造器 === 构造子  === Constructor 类  === Class List接口中常用类 -------------- Vector:线程安全,但速度慢,已被ArrayList替代. ArrayList:线程不安全,查询速度快. LinkedList:链表结构,增删速度快. 取出LIst集合中元素的方式: get(int  index):通过脚标获取元素.