博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
javascript中文版教程(转自 幸福清扬)
阅读量:5107 次
发布时间:2019-06-13

本文共 4148 字,大约阅读时间需要 13 分钟。

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

 

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

 

学习迭代模式

 à

想到了对应.NET中的foreach

 à

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

à

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

 

一、             迭代模式

 

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

 

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

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

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

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

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

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

 

二、             Foreach

 

迭代模式不就是实现集合遍历嘛,这让我想到了.NET2.0新增的语法foreach,foreach就是用来对集合实现遍历的,所以我猜想能被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的类型安全”,深感认同。(不过,原话不是这么说了,原话是说的java的for/in语法,我自己翻译了一下)

 

四、.NET中的迭代器

       好了,再回到迭代模式。.NET Framework 已经将迭代模式做进了框架内所有定义的集合类型内,MSDN中称这个所有集合都能返回的接口IEnumerator为迭代器(和java的Iterator对应)。从单一职责的角度考虑,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的约束问题(还记得吗?)。正因为迭代器是编译器默认生成的,而且是用户不可见的,而且是临时的,所以尝试对它的修改和引用注定是不被允许的,当然了,遍历看一看,读取一下总是可以的。

 

 

学习参考

 

生活TMD需要激情,做事需冷静,说话需冷静!

遇事记着:办法总比困难多,困难和问题说不定就是机遇和转折!

历史证明:哪个环节没照顾到,哪个环节就会出问题!能自己来,就不要让别人来。

转载于:https://www.cnblogs.com/zhcw/archive/2012/11/29/2795566.html

你可能感兴趣的文章
Python 生成哈希hash--hashlib模块
查看>>
myeclipse插件安装
查看>>
最近看NCZ的JS高级程序设计整理的一些代码
查看>>
浙江省第十二届省赛 Beauty of Array(思维题)
查看>>
NOIP2013 提高组 Day1
查看>>
UVA 1602 Lattice Animals
查看>>
bzoj千题计划219:bzoj1568: [JSOI2008]Blue Mary开公司
查看>>
[笔记]STM32使用非8M晶振时如何修改代码
查看>>
个人对vue生命周期的理解
查看>>
cocos2dx 3.x simpleAudioEngine 长音效被众多短音效打断问题
查看>>
Section 1.2 dualpal
查看>>
存储(硬件方面的一些基本术语)
查看>>
Dithering-视觉的奇特现象
查看>>
观察者模式
查看>>
转】MyEclipse使用总结——MyEclipse文件查找技巧
查看>>
Weka中数据挖掘与机器学习系列之基本概念(三)
查看>>
Java-文件上传和下载
查看>>
Memory and Trident(CodeForces 712B)
查看>>
Win磁盘MBR转换为GUID
查看>>
大家在做.NET B/S项目的时候多用什么设技术啊?
查看>>