幸福清扬

之技术学习

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
 

关键词:迭代模式、foreach、迭代器、IEnumerableIEnumerator、泛型

 

导言:这两天看迭代模式,一边学习,一边联系到.NET Framework的有关设计,小有收获,写下此文以供日后查看。这个关联学习的过程包括以下几个阶段:

 

学习迭代模式

 à

想到了对应.NET中的foreach

 à

在深入了解foreach的时候碰到了泛型

à

然后回归到.NET集合内的迭代,认识了到了“迭代器”的说法

 

一、             迭代模式

 

定义提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其中内部的表示。

 

关于迭代模式的正统解释和示例说明,不想多说,参考《HeadFirst》或《设计模式》。谈谈个人的理解:

       首先,迭代模式出现在对集合遍历的需求上。

集合(C)有很多形式:数组、ArrayListHashTable等等,如果要对这些集合的实例变量做遍历,大的方式和流程是一致的,但是具体实现代码不尽相同;

为了尽量减少代码数量和尽可能的实现逻辑抽象,就有人想出了Iterator(迭代接口),让这些集合都实现这个接口,以方便对接口的统一调用;

但是如此就违反了OO设计的“职责单一”原则,所以需要另外设置一个类(E)来实现Iterator接口,当然这个接口需要以集合为参数。

获取某个集合的迭代接口对象,可以放在E内实现,也可以放在C内实现。从用户角度考虑,面向用户的信息量要尽量精简、屏蔽实现细节,所以最好放在C内,这也不怎么违背集合C的职责单一性。

 

二、             Foreach

 

迭代模式不就是实现集合遍历嘛,这让我想到了.NET2.0新增的语法foreachforeach就是用来对集合实现遍历的,所以我猜想能被foreach的集合可定实现了迭代,所以我马上参看了《C#语言规范》关于foreach的说明:

5.3.3.16 foreach 语句(因为没有参看上下文,没太看懂,抽时间再看)

8.8.4 foreach 语句(看懂了,说两句)

 

C#语言规范》的相关全文不做全部转载,自行参考。我关心的有几句话:

首先是foreach的使用限制: foreach 语句执行期间,迭代变量表示当前正在为其执行迭代的集合元素。如果嵌入语句试图修改迭代变量(通过赋值或 ++ -- 运算符)或将迭代变量作为 ref out 参数传递,则将发生编译时错误。

为什么会这样呢?这是我的一个疑问,我后边会解释。

其次,C#语言规范》明确说明了什么叫集合:如果类型 C 实现了 System.Collections.IEnumerable 接口,或者能够满足下列(有关实现集合模式的)条件,就称它是集合类型:

·      C 包含一个 public 实例方法,它带有签名 GetEnumerator(),且返回值属于结构类型、类类型或接口类型(后文中用 E 表示该返回值的类型)。

·     E 包含一个 public 实例方法,此方法具有签名 MoveNext() 和返回类型 bool

·     E 包含一个名为 Current public 实例属性,此属性允许读取当前值。此属性的类型称为该集合类型的元素类型。

 

我为什么会留意这一点呢,因为规范上说:foreach 语句的表达式的类型必须是集合类型。

老实说,之前我对foreach的实现原理总有种神秘感,现在了解了其原理之后,手痒痒的厉害,就试着做了个测试:自己定义一个集合,但不实现IEnumerable接口,代码如下:

 

using System;

using System.Collections;

using System.Collections.Generic;

using System.Text;

 

namespace 迭代

{

    class Program

    {

        static void Main(string[] args)

        {

            MyIEnumerator mi = new MyIEnumerator();

            foreach (int i in mi)

            {

                Console.WriteLine(i);

            }

 

            Console.Read();

        }

    }

 

    class MyIEnumerator //: IEnumerator

    {

        int i;

 

        public MyIEnumerator GetEnumerator()

        {

            return this;

        }

 

        public MyIEnumerator()

        {

            i = -1;

        }

 

        public object Current

        {

            get { return i; }

        }

 

        public bool MoveNext()

        {

            if (i < 3)

            {

                i++;

                return true;

            }

            else

            {

                return false;

            }

        }

 

        //public void Reset()

        //{

        //    i = -1;

        //}

    }

}

 

       这段程序测试通过,兴奋!不过兴奋归兴奋,以后实际使用还是继承IEnumerable来的规范一点。

 

最后,《规范》给出了foreach的背后扩展形式

E enumerator = (collection).GetEnumerator();

try {

   while (enumerator.MoveNext()) {

       ElementType element = (ElementType)enumerator.Current;

       statement;

   }

}

finally {

   IDisposable disposable = enumerator as System.IDisposable;

   if (disposable != null) disposable.Dispose();

}

并说,enumerator 变量是一个临时变量,它在嵌入 statement 中既是不可访问的,也是不可见的,元素变量在嵌入 statement 中是只读的。

这倒解释了,前边使用了foreach的限制,那enumerator为什么又是个临时变量呢?这又是个疑问留在后边解释。

 

三、           泛型

可爱的泛型,自从我了解到什么是泛型之后,我就有一种说不的快乐,哈哈,原来泛型这么好使,这里我不打算详细说我对泛型的理解,我对它的喜爱足以让我另立专题,这里我只想说说,我怎么从foreach找到了泛型。

我们一般新建一个cs文件(类文件)时,VS2005默认就给出了三个引用项

using System;

using System.Collections.Generic;

using System.Text;

 

我在认识IEnumerator接口的时候,尝试写这么一句话

IEnumerator E = list. GetEnumerator(),却发现编辑环境居然不自动提示IEnumerator这个单词,而且我写出来也不变色(是类或接口就应该变成绿色嘛),奇怪了(IEnumerator是声明在System.Collections命名空间内的,我忘了引用,要是出来才奇怪!)

,却出来了个IEnumerator<T>的提示,泛型?不了解,决定翻翻书……

       关于泛型,我还想说的是,三个默认的引用文件就有其一个System.Collections.Generic,说明其何等的重要。

       另外一句话,是刚刚看设计模式得到的“使用泛型来确保foreach的类型安全”,深感认同。(不过,原话不是这么说了,原话是说的javafor/in语法,我自己翻译了一下)

 

四、.NET中的迭代器

       好了,再回到迭代模式。.NET Framework 已经将迭代模式做进了框架内所有定义的集合类型内,MSDN中称这个所有集合都能返回的接口IEnumerator为迭代器(和javaIterator对应)。从单一职责的角度考虑,GetEnumerator()方法所返回的接口实例,应该是实现集合遍历的另外一个类,这个类呢?我通过Reflector查看源码没有看到,上网查了查,原来微软将迭代器做进了编译器,真绝!不仅如此,为了方便用户实现自己的迭代器,微软还为迭代器的快捷生成设置了 yield return 这样的语法,如下例。关于yield是什么,请自行参看MSDN帮助,我的白话理解是:能够允许连续返回多个值,而且返回的这多个值就构成了一个可以遍历的迭代器,这是编译器自己干的。

 

public class Persons : IEnumerable

{

    string[] m_Names;

 

    public Persons(params string[] Names)

    {

        m_Names = new string[Names.Length];

 

        Names.CopyTo(m_Names,0);

    }

 

    public IEnumerator GetEnumerator()

    {

        foreach (string s in m_Names)

        {

            yield return s;

        }

    }

}

 

class Program

{

    static void Main(string[] args)

    {

        Persons arrPersons = new Persons("Michel","Christine","Mathieu","Julien");

 

        foreach (string s in arrPersons)

        {

            Console.WriteLine(s);

        }

 

        Console.ReadLine();

    }

}

 

最后,顺便解释一下,foreach的约束问题(还记得吗?)。正因为迭代器是编译器默认生成的,而且是用户不可见的,而且是临时的,所以尝试对它的修改和引用注定是不被允许的,当然了,遍历看一看,读取一下总是可以的。

 

 

学习参考

.NET设计模式(18):迭代器模式(Iterator Pattern

http://www.cnblogs.com/Terrylee/archive/2006/09/16/iterator_pattern.html

C# 2.0 新特性之迭代器, Yield Return

http://montaque.cnblogs.com/archive/2005/04/21/142844.html

疑问:yield到底是怎么运作的?  

http://blog.csdn.net/kangfucat/archive/2007/08/21/1752239.aspx

posted on 2008-04-02 12:19  杨连国  阅读(262)  评论(0编辑  收藏  举报