C#特性-迭代器(上)及一些研究过程中的副产品

提到迭代器我们不能不想到迭代器模式,那我就以迭代器模式作为开场白.

在我们的应用程序中常常有这样一些数据结构:

它们是一个数据的集合,如果你知道它们内部的实现结构就可以去访问它们,它们各自的内部存储结构互不相同,各种集合有各自的应用场合.说到这里大家可能想出一大堆这样的集合了:List,Hashtable,ArrayList等等。这些集合各自都有各自的个性,这就是它们存在的理由。但如果你想遍历它你必须知道它内部的存储细节,作为一个集合元素,把内部细节暴露出来肯定就不好了,这样客户程序就不够稳定了,在你更换集合对象的时候,比如List不能满足需求的时候,你换成Hashtable,因为以前的客户程序过多的关注了List内部实现的细节,所以不能很好的移植。而迭代器模式就是为解决这个问题而生的:

提供一种一致的方式访问集合对象中的元素,而无需暴露集合对象的内部表示。
比如现在有这样一个需求,遍历集合内的元素,然后输出,但是并不限定集合是什么类型的集合,也就是未来集合可能发生改变。

思考:

集合会发生改变,这是变化点,集合改变了,遍历方法也改变,我们要保证遍历的方法稳定,那么就要屏蔽掉细节。找到了变化点那我们就将其隔离起来(一般使用interface作为隔离手段):假设所有的集合都继承自ICollection接口,这个接口用来隔离具体集合的,将集合屏蔽在接口后面,作为遍历我们肯定需要这样一些方法:MoveNext,Current,既然ICollection负责数据存储,职责又要单一,那么就新建立一个接口叫做Iterator吧,每种具体的集合都有自己相对应的Iterator实现:

下面是一个简易的实现代码:

/// <summary>
/// 集合的接口
/// </summary>
  public interface ICollection
  {
    int Count { get; }
    /// <summary>
    /// 获取迭代器
    /// </summary>
    /// <returns>迭代器</returns>
    Iterator GetIterator();
  }
  /// <summary>
  /// 迭代器接口
  /// </summary>
  public interface Iterator
  {
    bool MoveNext();

    object Current { get; }
  }

  public class List : ICollection
  {
    private const int MAX = 10;
    private object[] items;
    public List()
    {
      items = new object[MAX];
    }
    public object this[int i]
    {
      get { return items[i]; }
      set { this.items[i] = value; }
    }
    #region ICollection Members

    public int Count
    {
      get { return items.Length; }
    }

    public Iterator GetIterator()
    {
      return new ListIterator(this);
    }

    #endregion
  }
  public class ListIterator : Iterator
  {
    private int index = 0;
    private ICollection list;
    public ListIterator(ICollection list)
    {
      this.list = list;
      index = 0;
    }
    #region Iterator Members

    public bool MoveNext()
    {
      if (index + 1 > list.Count)
        return false;
      else
      {
        index++;
        return true;
      }
    }

    public object Current
    {
      get { return list[index]; }
    }

    #endregion
  }
  /// <summary>
  /// 测试
  /// </summary>
  public class Program
  {
    static void Main()
    {
      ICollection list = new List();
      Iterator iterator = list.GetIterator();
      while (iterator.MoveNext())
      {
        object current = iterator.Current;
      }
    }
}

看看最后的测试,是不是不管具体的集合如何改变,遍历代码都非常稳定?而且扩展新的集合类也非常方便,只是添加代码不会修改原来的代码,符合开闭原则。当然,这么好的解决方案微软当然不会放过,现在C# 2.0里已经内置了对迭代器的支持,看看System.Collections, System.Collections.Generic命名空间,所有的集合都实现了这个接口:IEnumerable,这个接口还有泛型的版本。注意到这个接口只有一个方法:IEnumerator GetEnumerator();,IEnumerator就是迭代器的接口,相当于我的实例里面的Iterator,它也有泛型的版本。
那么现在在.net里所有的集合类都可以这样访问了:

代码如下:

IEnumerator ienumerator = list.GetEnumerator();
while(ienumerator.MoveNext())
{
    object current = ienumerator.Current;
}

但是这样访问也太麻烦了,所以C#里出现了foreach关键字,我们来看看foreach背后发生了什么?假如有如下的代码:

public static void Main()
{
      ArrayList list = new ArrayList();
      list.Add(1);
      list.Add(2);
      list.Add(3);
      foreach (object item in list)
      {
        Console.WriteLine(item.ToString());
      }
}

下面是它对应的IL代码:

.method private hidebysig static void Main() cil managed
{
  .entrypoint
  .maxstack 2
  .locals init (
    [0] class [mscorlib]System.Collections.ArrayList list,
    [1] object item,
    [2] class [mscorlib]System.Collections.IEnumerator CS$5$0000,
    [3] class [mscorlib]System.IDisposable CS$0$0001)
  L_0000: newobj instance void [mscorlib]System.Collections.ArrayList::.ctor()
  L_0005: stloc.0
  L_0006: ldloc.0
  L_0007: ldc.i4.1
  L_0008: box int32
  L_000d: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object)
  L_0012: pop
  L_0013: ldloc.0
  L_0014: ldc.i4.2
  L_0015: box int32
  L_001a: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object)
  L_001f: pop
  L_0020: ldloc.0
  L_0021: ldc.i4.3
  L_0022: box int32
  L_0027: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object)
  L_002c: pop
  L_002d: ldloc.0
  L_002e: callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Collections.ArrayList::GetEnumerator()
  L_0033: stloc.2
  L_0034: br.s L_0048
  L_0036: ldloc.2
  L_0037: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
  L_003c: stloc.1
  L_003d: ldloc.1
  L_003e: callvirt instance string [mscorlib]System.Object::ToString()
  L_0043: call void [mscorlib]System.Console::WriteLine(string)
  L_0048: ldloc.2
  L_0049: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
  L_004e: brtrue.s L_0036
  L_0050: leave.s L_0063
  L_0052: ldloc.2
  L_0053: isinst [mscorlib]System.IDisposable
  L_0058: stloc.3
  L_0059: ldloc.3
  L_005a: brfalse.s L_0062
  L_005c: ldloc.3
  L_005d: callvirt instance void [mscorlib]System.IDisposable::Dispose()
  L_0062: endfinally
  L_0063: call string [mscorlib]System.Console::ReadLine()
  L_0068: pop
  L_0069: ret
  .try L_0034 to L_0052 finally handler L_0052 to L_0063
}

从.locals init 那里可以看出编译器为我们添加了两个局部变量,一个就是迭代器。

代码如下:

L_002d: ldloc.0
L_002e: callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Collections.ArrayList::GetEnumerator()
L_0033: stloc.2

这三行代码告诉我们,调用list的GetEnumerator()方法,获取迭代器实例将其赋值给编译器为我们添加的那个迭代器局部变量,接着是L_0034: br.s L_0048,
br.s这个指令是强制跳转,我们接着看

代码如下:

L_0048: ldloc.2
L_0049: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()

调用迭代器的MoveNext()方法,L_004e: brtrue.s L_0036 如果是true的话跳转,

代码如下:

L_0036: ldloc.2
L_0037: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
L_003c: stloc.1
L_003d: ldloc.1
L_003e: callvirt instance string [mscorlib]System.Object::ToString()
L_0043: call void [mscorlib]System.Console::WriteLine(string)

获取当前值,然后输出
 看到没有,实际foreach后面干的事就是获取迭代器,然后一个while循环,不过这样一些确实简洁多了。
说到这里是不是

代码如下:

IEnumerator ienumerator = list.GetEnumerator();
            while (ienumerator.MoveNext())
            {
                object item = ienumerator.Current;
                Console.WriteLine(item.ToString());
            }

代码如下:

foreach (object item in list)
            {
                Console.WriteLine(item.ToString());
            }

这两样代码是一样的呢?如果不一样那推荐使用哪一个呢?当然是使用第二种,简洁嘛,除了简洁之外就没有其它的了?细心读者会发现上面的IL代码,
在结束循环后还有一大块,可我们的C#代码中并没有啊,接着分析:
.try L_0034 to L_0052 finally handler L_0052 to L_0063
这里说明从L_0034到L_0052是被放在try里面的,恰好这段代码是循环体里的东西,L_0052到L_0063里是放在finally里的,看来foreach还给我们加了一
个try{}finally{}结构啊。那看看L_0052到L_0063里是什么东西吧:

代码如下:

L_0052: ldloc.2
    L_0053: isinst [mscorlib]System.IDisposable
    L_0058: stloc.3
    L_0059: ldloc.3
    L_005a: brfalse.s L_0062
    L_005c: ldloc.3
    L_005d: callvirt instance void [mscorlib]System.IDisposable::Dispose()
    L_0062: endfinally
    L_0063: call string [mscorlib]System.Console::ReadLine()

判断迭代器对象是否是一个IDisposable实例,如果是,那就要调用它的Dispose()方法了(为啥它要实现IDisposable接口?那肯定这个迭代器里使用了一些非托管资源)。
看到了吧,foreach也真够智能的,看来使用foreach的方式是比自己用while方式安全稳定多了。

(PS:好像是扯远了点,不过大家一起了解一下,呵呵,其实我当初也没想说这个,不过后来看IL代码有点不对劲,就当作个副产品吧)

C# 2.0里还出现个关键字yield,我看了半天MSDN也没明白它的意思:

在迭代器块中用于向枚举数对象提供值或发出迭代结束信号。到现在还是没明白,不过yield这个东西后面却包含了很多东西,有一些非常“奇怪”的特性,
我称之为奇怪的意思是与我们以前的思维有的不符,Linq的一些特质也是建立在这个特性之上的。关于yield的更多讨论我想放在另外一篇文章中,因为我觉得有必要。

(0)

相关推荐

  • C#使用yield关键字构建迭代器详解

    以前,如果我们希望构建支持foreach枚举的自定义集合,只能实现IEnumerable接口(可能还有IEnumerator()),返回值还必须是IEnumerator类型,除此之外还可以通过迭代器来使用构建foreach循环的类型,详细见下链接. 代码 public class Car { //内部状态数据 public int CurentSpeed; public int MaxSpeed; public string name; //汽车能不能用 private bool carIsde

  • C#中使用迭代器处理等待任务

     介绍 可能你已经阅读 C#5 关于 async 和 await 关键字以及它们如何帮助简化异步编程的,可惜的是在升级VS2010后短短两年时间,任然没有准备好升级到VS2012,在VS2010和C#4中不能使用异步关键字,你可能会想 "如果我能在VS 2010中写看起来同步的方法,但异步执行.我的代码会更清晰." 看完这篇文章后,您将能够做到这一点.我们将开发一个小的基础结构代码,让我们写"看起来同步的方法,但异步执行"的方法,这个VS2012 异步关键字一样,

  • C#特性 迭代器(下) yield以及流的延迟计算

    从0遍历到20(不包括20),输出遍历到的每个元素,并将大于2的所有数字放到一个IEnumerable<int>中返回 解答1:(我以前经常这样做) static IEnumerable<int> WithNoYield() { IList<int> list = new List<int>(); for (int i = 0; i < 20; i++) { Console.WriteLine(i.ToString()); if(i > 2) l

  • C#迭代器模式(Iterator Pattern)实例教程

    本文以实例形式简单简述了C#迭代器模式的实现方法,分享给大家供大家参考.具体方法如下: 一般来说,迭代器模式的需求来自:需要对一些集合进行迭代,而迭代的方式可能有很多种. 说到迭代,动作大致包括设置第一个位置,获取下一个位置元素,判断是否迭代结束,获取当前位置元素,大致就这么些.把这些迭代动作封装到一个接口中. public interface IIterator { void First(); string Next(); bool IsDone(); string Current(); }

  • C#特性-迭代器(上)及一些研究过程中的副产品

    提到迭代器我们不能不想到迭代器模式,那我就以迭代器模式作为开场白. 在我们的应用程序中常常有这样一些数据结构: 它们是一个数据的集合,如果你知道它们内部的实现结构就可以去访问它们,它们各自的内部存储结构互不相同,各种集合有各自的应用场合.说到这里大家可能想出一大堆这样的集合了:List,Hashtable,ArrayList等等.这些集合各自都有各自的个性,这就是它们存在的理由.但如果你想遍历它你必须知道它内部的存储细节,作为一个集合元素,把内部细节暴露出来肯定就不好了,这样客户程序就不够稳定了

  • c# Newtonsoft 六个值得使用的特性(上)

    一:讲故事 看完官方文档,阅读了一些 Newtonsoft 源码,对它有了新的认识,先总结 六个超经典又实用的特性,同大家一起分享,废话不多说,快来一起看看吧~~~ 二:特性分析 1. 代码格式化 如果你直接使用 JsonConvert.SerializeObject的话,默认情况下所有的json是挤压在一块的,特别不方便阅读,如下所示: static void Main(string[] args) { var reportModel = new ReportModel() { Product

  • 基于jquery ajax的多文件上传进度条过程解析

    这篇文章主要介绍了基于jquery ajax的多文件上传进度条过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 效果图 前端代码,基于jquery <!DOCTYPE html> <html> <head> <title>主页</title> <meta name="viewport" content="width=device-width,initia

  • win10上安装ubunt18双系统过程中出现mmx64.efi not found问题

    安装Ubuntu18过程中,从u盘启动ubunt安装,出现mmx64.efi not found问题 如下图: 制作好ubunt启动盘之后在EFI/BOOT下会看到两个文件,将其中grubx64.efi改名为mmx64.efi即可重装系统. 总结 以上所述是小编给大家介绍的win10上安装ubunt18双系统过程中出现mmx64.efi not found问题,希望对大家有所帮助!

  • 详解mysql8.018在linux上安装与配置过程

    windows下安装介绍:去看看–>mysql8.018在windows下安装介绍 Linux平台: 以下操作以mysql 8.0.18,系统为Ubuntu 16.04.6 LTS (GNU/Linux 4.4.0-142-generic x86_64)为例: A. 自动安装 sudo apt-get install mysql-server sudo apt-get install mysql-client sudo apt-get install libmysqlclient-dev B.

  • C++特性:迭代器

    1. 迭代器(Iterator)的介绍 背景:指针可以用来遍历存储空间连续的数据结构,但是对于存储空间费连续的,就需要寻找一个行为类似指针的类,来对非数组的数据结构进行遍历. 定义:迭代器是一种检查容器内元素并遍历元素的数据类型. 迭代器提供对一个容器中的对象的访问方法,并且定义了容器中对象的范围. 迭代器(Iterator)是指针(pointer)的泛化,它允许程序员用相同的方式处理不同的数据结构(容器). (1)迭代器类似于C语言里面的指针类型,它提供了对对象的间接访问. (2)指针是C语言

  • java 对象实例化过程中的多态特性解析

    目录 java 对象实例化过程中的多态特性 通过案例说明 通过上述代码 java对象的三个特性(封装.继承.多态) 1.封装 2.继承 3.多态 java 对象实例化过程中的多态特性 执行对象实例化过程中遵循多态特性 ==> 调用的方法都是实例化的子类中的重写方法,只有明确调用了super.xxx关键词或者是子类中没有该方法时,才会去调用父类相同的同名方法. 通过案例说明 package com.njau.test1; class Test { public static void main(S

  • 详解敏捷过程中的需求管理

    问题分析 在交流中,笔者了解到每家公司的情况: 第一家企业在第一个迭代认领了15个故事,团队很容易就完成了:老板觉得以团队的能力可以做到每个迭代完成30个故事,于是后续每个迭代都希望团队认领30个故事,团队认领30个任务后,累死累活只能完成20左右的故事: 第二家企业研发团队8人,每个迭代总有两个成员工作完不成:团队每天早会正常开,但是总感觉那两个成员整个迭代都在做那一两个故事,做的功能也没啥进展,有时候还做不完: 第三家企业使用了一个新框架,近两个迭代团队按以往的速率进行任务认领,结果由于团队

  • 浅谈springfox-swagger原理解析与使用过程中遇到的坑

    swagger简介 swagger确实是个好东西,可以跟据业务代码自动生成相关的api接口文档,尤其用于restful风格中的项目,开发人员几乎可以不用专门去维护rest api,这个框架可以自动为你的业务代码生成restfut风格的api,而且还提供相应的测试界面,自动显示json格式的响应.大大方便了后台开发人员与前端的沟通与联调成本. springfox-swagger简介 签于swagger的强大功能,java开源界大牛spring框架迅速跟上,它充分利用自已的优势,把swagger集成

随机推荐