4.1迭代器块
迭代器块就是产生值的有序序列的语句块,迭代器块通过一个/多个yield语句区别于常规语句:
-yield return 语句产生迭代的下一个值
-yield break 语句指明迭代完成
迭代器块可以用做一个方法体、运算符体、访问器体,前提是对应函数成员的返回类型是枚举器接口之一/可枚举接口之一。
迭代器块在C#语法中不是独特的元素,它们在几个方面受限制,并且主要作用在函数成员声明的语义上,它们在语法上只是语句块而已。当一个函数成员使用迭代器块实现时,该函数成员的正式参数列表指定任何ref/out参数都将导致编译时错误;return出现在迭代器块中也会导致编译时错误,但yield return语句是允许的;在迭代器块中包含不安全上下文也会导致编译时错误,即便迭代器声明在不安全上下文中,迭代器块总是定义为一个安全上下文。
4.1.1 枚举器接口
枚举器接口是System.Collections.IEnumerator接口和System.Collections.Generic.IEnumerator<T>的所有实例。
4.1.2 可枚举接口
可枚举接口是System.Collections.IEnumerable接口和System.Collections.Generic.IEnumerable<T>的所有实例。
4.1.3 yield类型
迭代器块生成具有相同类型的所有值的序列。该类型被称为迭代器块的yield类型:
-通常用于实现返回IEnumerator/IEnumerable的函数成员的迭代器块的yield类型是object;
-通常用于实现返回IEnumerator<T>/IEnumerable<T>的函数成员的迭代器块的yield类型是T。
4.1.4 this访问
在类的实例成员的迭代器块内,this表达式是值,该值的类型是类类型。在这个类型内可以采用这种用法,这个值就是成员被调用时对对象的引用。
在结构的实例成员的迭代器块内,this表达式被当作一个变量,该变量的类型就是结构类型。在这个结构内它可以采用这种用法,该变量表示一个成员被调用时的对应结构的一个拷贝。在结构实例成员的迭代器块内,this变量的行为就好像是结构类型的一个值参数。
4.2枚举对象
当返回枚举器接口类型的函数成员使用迭代器块实现时,调用函数成员并不会立即执行迭代器块中的代码,而是先创建并返回枚举器对象,该对象封装了在迭代器块中指定的代码;当枚举器对象对象的MoveNext方法被调用时,迭代器块中的代码才会被执行。枚举器对象的特征:
-它实现了IEnumerator和IEnumerator<T>,T是迭代器块的yield类型;
-它实现了System.IDisposable;
-它使用实参值的拷贝和传递给函数成员的实例值进行初始化;
-它由四个潜在状态:before、running、suspended、after,并且在before状态之前被初始化。
枚举器对象通常是一个编译器生成的枚举器类实例,它封装了迭代器语句块中的代码,并且实现了枚举器接口,但其他实现方法也可以。如果一个枚举器类是由编译器生成的,这个类将会是内嵌的,在包含函数成员的类中,类将具有私有可访问性,并且该类具有一个保留为编译器所用的名称。
枚举器对象可以实现比在此指定的更多接口,下面就描述由IEnumerable和IEnumerable<T>接口实现的MoveNext,Current,Dispose成员的确切行为,这两个接口由枚举器对象提供。这里需要注意一点:枚举器对象不支持IEnumerator.Reset方法。调用该方法将会抛出System.NotSupportedException异常。
4.2.1 MoveNext方法
枚举器对象的MoveNext方法封装了迭代器块的代码,调用MoveNext方法将会执行迭代器内的代码,并将枚举对象的Current属性设置为适当的值。由MoveNext方法执行的精确动作,取决于MoveNext方法被调用时枚举器对象的状态:
-如果枚举器对象的状态是before,调用MoveNext:将会把状态改为running,把迭代器块的参数初始化为枚举器对象初始化而保留的实参值和实例值,然后开始执行迭代器块直到被中断;
-如果枚举器对象的状态是running,调用MoveNext的结果是未指定的;
-如果枚举器对象的状态是suspended,调用MoveNext:将会把状态改成running;
-将所有局部变量和参数的值回复为迭代器最后一次挂起suspended时执行状态保留的值;由这些变量所引用的任何对象的内容,都可能因为前一次对MoveNext的调用而改变:在引发执行挂起的yield return语句之后重新开始执行迭代器块,并且这个状态会继续,直到执行被中断;
-如果枚举器对象的状态时after,那么调用MoveNext将返回false。
当MoveNext执行迭代器块时,有四种方法可以中断执行:
-当遇到一个yield return语句时:在该语句中被给定的表达式将被求值,隐式地转换到yield类型,并被赋值给枚举对象的Current属性;枚举器体的执行将被挂起,所有局部变量的值和参数被保留,该yield return语句的位置也被保存,如果yield return语句在一个/多个try块之内,与之关联的finally块在此时将不会执行;枚举器对象的状态也会被改为suspended。MoveNext方法对调用方返回true,表明迭代器成功进入到下一个值;
-当遇到yield break语句时:若yield break语句在一个/多个try块之内,与之关联的finally语句将被执行;枚举对象的状态改为after;MoveNext方法对调用方返回false,表明迭代已经完成;
-当遇到迭代器块的结束点时:枚举器对象的状态改成after,MoveNext方法对调用方返回false,表明迭代已经完成;
-当一个一次被抛出并被传播到迭代器块之外时:在迭代器块之内将会由于异常传播而执行合适的finally块;枚举器对象的状态改成after,对于MoveNext方法的调用方来说,异常传播将会继续。
4.2.2 Current属性
枚举器对象的Current属性受到迭代器块的yield return语句的影响。当枚举器对象处于suspended状态时,Current的值就是最后一次调用MoveNext时被设置的值;当枚举对象处于before,running或after时,访问Current的所得结果是未指定的。
对于一个具有非object类型的yield类型迭代器块,通过枚举器对象的IEnumerable实现访问Current所得实现,对应于通过枚举器对象的IEnumerator<T>访问Current所得实现,并将结果转换到object类型。
4.2.3 Dispose方法
Dispose方法通过将枚举器对象的状态设置为after,从而清理迭代结果:
-如果枚举器对象的状态是before,调用Dispose将改变其状态为after;
-如果枚举器对象的状态是running,调用Dispose的结果是为指定的;
-如果枚举器对象的状态是suspended,调用Dispose会改变其状态为running;执行finally块,就好像最后执行的yield return语句是一个yield break语句,如果这会导致一个异常被抛出并传播到迭代器体之外,则枚举器对象的状态将被设置为after,该异常Dispose方法的调用方;
-如果枚举器对象的状态为after,那么调用Dispose没有效果。
4.3可枚举对象
当返回一个可枚举接口类型的函数成员使用迭代器块实现时,调用函数成员同样不会立即执行迭代器块代码,而是先创建并返回一个可枚举对象,可枚举对象的GetEnumerator方法返回一个枚举器对象,它封装了在迭代器块中指定的代码,但枚举器对象的MoveNext方法被调用时,将会触发迭代器块代码的执行。可枚举对象具有的特征:
-它实现了IEnumerable和IEnumerable<T>接口,T是迭代器块的yield type类型;
-它使用实参值的拷贝进行初始化,并将实例值传递给函数成员。
可枚举对象通常是一个由编译器生成的可枚举类的实例,该类封装了迭代器块的代码,并实现了可枚举接口,但其他实现方法也是可以的。如果可枚举类由编译器生成,该类将内嵌在包含函数成员二点类中,并具有私有可访问性,以及一个为编译器所保留使用的名字。
可枚举对象可以实现更多的接口。具体来说,可枚举对象也可以实现IEnumerator和IEnumerator<T>接口,这使得它既可以作为可枚举对象,又可以作为枚举器对象。在该实现类型中,对可枚举对象的GetEnumerator方法的首次调用,将会返回可枚举对象自身;对于该可枚举对象的GetEnumerator方法的后续调用,将会返回可枚举对象的一个拷贝,所以每次返回的枚举器将会有它自己的状态,并且在一个枚举器中所做的改变不会影响另一个枚举器。
可枚举对象提供IEnumerable和IEnumerable<T>接口的GetEnumerator方法的一个实现,这两个GetEnumerator方法共享一个得到并返回一个有效的枚举器对象的公共实现。
枚举器对象使用可枚举对象被初始化时所使用的实参值和实例值进行初始化。
4.4yield语句
yield语句用于迭代器块:以产生一个枚举器对象的值,或表明迭代的结束。
为了确保与现存程序的兼容性,yield并不是一个保留字,并且只有在紧邻return/break关键词之前才具有特别的意义,至于在其他上下文,是可以用做标识符的。yield语句所能出现的地方有几个限制:
-yield语句出现在方法体、运算符体、访问器体之外时,将会导致编译时错误;
-yield语句出现在匿名方法中时,将会导致编译时错误;
-yield语句出现在try语句的finally从句中,将会导致编译时错误;
-yield语句之一的yield return语句,当它出现在包含catch子语句的任何try语句中任何位置时,将会导致编译时错误。
从yield return语句中表达式类型到迭代器的yield类型,必须存在隐式转换,yield return语句按如下方式执行:
-在语句中给出的表达式将被求值,隐式地转换到yield类型,并被赋给枚举器对象的Current属性;
-迭代器块的执行将被挂起。如果yield return语句在一个/多个tyr块中,与之关联的finally块此时将不会执行;
-枚举器对象的MoveNext方法对调用方返回true,表明枚举器对象成功进行到下一项。
对于枚举器对象的MoveNext方法的下一次调用,重新从迭代器块挂起的地方开始执行。yield break语句按如下方式执行:
-如果yield break语句被包含在一个/多个带finally块的try块中,初始控制权将转移到最里面的try语句的finally块;当控制到达finally块的结束点后,控制权会转移到下一个最近的try语句的finally块。这个过程会一直重复,直到所有内部的try语句的finally块都被执行;
-当控制放回到迭代器块的调用方,可能是由于枚举器对象的MoveNext方法或Dispose方法。
对于一个yield return expr形式的yield return语句stmt:
-想stmt开始一样,在expr的开头变量v就有明确的赋值状态;
-如果在expr的结束点v被明确赋值,那么它在stmt的结束点也将被明确赋值;否则在stmt结束点将不会被明确赋值。