本文主要记录我在学习C#中foreach遍历原理的心得体会。

对集合中的要素进行遍历是所有编码中经常涉及到的操作,因此大部分编程语言都把此过程写进了语法中,比如C#中的foreach。经常会看到下面的遍历代码:

  1. var lstStr = new List<string> { "a", "b" };
  2. foreach (var str in lstStr)
  3. {
  4. Console.WriteLine(str);
  5. }

实际此代码的执行过程:

  1. var lstStr = new List<string> {"a", "b"};
  2. IEnumerator<string> enumeratorLst = lstStr.GetEnumerator();
  3. while (enumeratorLst.MoveNext())
  4. {
  5. Console.WriteLine(enumeratorLst.Current);
  6. }

会发现有GetEnumerator()方法和IEnumerator<string>类型,这就涉及到可枚举类型和枚举器的概念。

为了方便理解,以下为非泛型示例:

  1. // 摘要:
  2. // 公开枚举器,该枚举器支持在非泛型集合上进行简单迭代。
  3. public interface IEnumerable
  4. {
  5. // 摘要:
  6. // 返回一个循环访问集合的枚举器。
  7. //
  8. // 返回结果:
  9. // 可用于循环访问集合的 System.Collections.IEnumerator 对象。
  10. IEnumerator GetEnumerator();
  11. }

实现了此接口的类称为可枚举类型,是可以用foreach进行遍历的标志。

方法GetEnumerator()的返回值是枚举器,可以理解为游标。

  1. // 摘要:
  2. // 支持对非泛型集合的简单迭代。
  3. public interface IEnumerator
  4. {
  5. // 摘要:
  6. // 获取集合中的当前元素。
  7. //
  8. // 返回结果:
  9. // 集合中的当前元素。
  10. //
  11. // 异常:
  12. // System.InvalidOperationException:
  13. // 枚举数定位在该集合的第一个元素之前或最后一个元素之后。
  14. object Current { get; }
  15.  
  16. // 摘要:
  17. // 将枚举数推进到集合的下一个元素。
  18. //
  19. // 返回结果:
  20. // 如果枚举数成功地推进到下一个元素,则为 true;如果枚举数越过集合的结尾,则为 false。
  21. //
  22. // 异常:
  23. // System.InvalidOperationException:
  24. // 在创建了枚举数后集合被修改了。
  25. bool MoveNext();
  26. //
  27. // 摘要:
  28. // 将枚举数设置为其初始位置,该位置位于集合中第一个元素之前。
  29. //
  30. // 异常:
  31. // System.InvalidOperationException:
  32. // 在创建了枚举数后集合被修改了。
  33. void Reset();
  34. }

而生成一个枚举器的的工具就是迭代器,以下是自定义一个迭代器的示例(https://msdn.microsoft.com/en-us/library/system.collections.ienumerator.aspx):

  1. using System;
  2. using System.Collections;
  3.  
  4. // Simple business object.
  5. public class Person
  6. {
  7. public Person(string fName, string lName)
  8. {
  9. this.firstName = fName;
  10. this.lastName = lName;
  11. }
  12.  
  13. public string firstName;
  14. public string lastName;
  15. }
  16.  
  17. // Collection of Person objects. This class
  18. // implements IEnumerable so that it can be used
  19. // with ForEach syntax.
  20. public class People : IEnumerable
  21. {
  22. private Person[] _people;
  23. public People(Person[] pArray)
  24. {
  25. _people = new Person[pArray.Length];
  26.  
  27. for (int i = ; i < pArray.Length; i++)
  28. {
  29. _people[i] = pArray[i];
  30. }
  31. }
  32.  
  33. // Implementation for the GetEnumerator method.
  34. IEnumerator IEnumerable.GetEnumerator()
  35. {
  36. return (IEnumerator) GetEnumerator();
  37. }
  38.  
  39. public PeopleEnum GetEnumerator()
  40. {
  41. return new PeopleEnum(_people);
  42. }
  43. }
  44.  
  45. // When you implement IEnumerable, you must also implement IEnumerator.
  46. public class PeopleEnum : IEnumerator
  47. {
  48. public Person[] _people;
  49.  
  50. // Enumerators are positioned before the first element
  51. // until the first MoveNext() call.
  52. int position = -;
  53.  
  54. public PeopleEnum(Person[] list)
  55. {
  56. _people = list;
  57. }
  58.  
  59. public bool MoveNext()
  60. {
  61. position++;
  62. return (position < _people.Length);
  63. }
  64.  
  65. public void Reset()
  66. {
  67. position = -;
  68. }
  69.  
  70. object IEnumerator.Current
  71. {
  72. get
  73. {
  74. return Current;
  75. }
  76. }
  77.  
  78. public Person Current
  79. {
  80. get
  81. {
  82. try
  83. {
  84. return _people[position];
  85. }
  86. catch (IndexOutOfRangeException)
  87. {
  88. throw new InvalidOperationException();
  89. }
  90. }
  91. }
  92. }
  93.  
  94. class App
  95. {
  96. static void Main()
  97. {
  98. Person[] peopleArray = new Person[]
  99. {
  100. new Person("John", "Smith"),
  101. new Person("Jim", "Johnson"),
  102. new Person("Sue", "Rabon"),
  103. };
  104.  
  105. People peopleList = new People(peopleArray);
  106. foreach (Person p in peopleList)
  107. Console.WriteLine(p.firstName + " " + p.lastName);
  108.  
  109. }
  110. }
  111.  
  112. /* This code produces output similar to the following:
  113. *
  114. * John Smith
  115. * Jim Johnson
  116. * Sue Rabon
  117. *
  118. */

在有了yield这个关键字以后,我们可以通过这样的方式来创建枚举器:

  1. using System;
  2. using System.Collections;
  3.  
  4. // Simple business object.
  5. public class Person
  6. {
  7. public Person(string fName, string lName)
  8. {
  9. this.firstName = fName;
  10. this.lastName = lName;
  11. }
  12.  
  13. public string firstName;
  14. public string lastName;
  15. }
  16.  
  17. // Collection of Person objects. This class
  18. // implements IEnumerable so that it can be used
  19. // with ForEach syntax.
  20. public class People : IEnumerable
  21. {
  22. private Person[] _people;
  23.  
  24. public People(Person[] pArray)
  25. {
  26. _people = new Person[pArray.Length];
  27.  
  28. for (int i = ; i < pArray.Length; i++)
  29. {
  30. _people[i] = pArray[i];
  31. }
  32. }
  33.  
  34. // Implementation for the GetEnumerator method.
  35. IEnumerator IEnumerable.GetEnumerator()
  36. {
  37. for (int i = ; i < _people.Length; i++)
  38. {
  39. yield return _people[i];
  40. }
  41. }
  42.  
  43. }
  44.  
  45. class App
  46. {
  47. static void Main()
  48. {
  49. Person[] peopleArray = new Person[]
  50. {
  51. new Person("John", "Smith"),
  52. new Person("Jim", "Johnson"),
  53. new Person("Sue", "Rabon"),
  54. };
  55.  
  56. People peopleList = new People(peopleArray);
  57. foreach (Person p in peopleList)
  58. Console.WriteLine(p.firstName + " " + p.lastName);
  59. }
  60. }

yield和return一起使用才有意义,这个关键字就是为迭代器服务的,所以有yield所在的方法是迭代器,作用是返回相同类型的值的一个序列,执行到yield return 这个语句之后,函数并不直接返回,而是保存此元素到序列中,继续执行,直到函数体结束或者yield break。

但是生成的此迭代器的函数体并不会在直接调用时运行,只会在foreach中迭代时执行。如:

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Reflection;
  5.  
  6. // Simple business object.
  7. public class Person
  8. {
  9. public Person(string fName, string lName)
  10. {
  11. this.firstName = fName;
  12. this.lastName = lName;
  13. }
  14.  
  15. public string firstName;
  16. public string lastName;
  17. }
  18.  
  19. // Collection of Person objects. This class
  20. // implements IEnumerable so that it can be used
  21. // with ForEach syntax.
  22. public class People
  23. {
  24. private Person[] _people;
  25.  
  26. public People(Person[] pArray)
  27. {
  28. _people = new Person[pArray.Length];
  29.  
  30. for (int i = ; i < pArray.Length; i++)
  31. {
  32. _people[i] = pArray[i];
  33. }
  34. }
  35.  
  36. // Implementation for the GetEnumerator method.
  37. public IEnumerable GetMyEnumerator()
  38. {
  39. Console.WriteLine("a");
  40. for (int i = ; i < _people.Length; i++)
  41. {
  42. Console.WriteLine(i.ToString());
  43. yield return _people[i];
  44. }
  45. }
  46.  
  47. }
  48.  
  49. class App
  50. {
  51. static void Main()
  52. {
  53. Person[] peopleArray = new Person[]
  54. {
  55. new Person("John", "Smith"),
  56. new Person("Jim", "Johnson"),
  57. new Person("Sue", "Rabon"),
  58. };
  59.  
  60. People peopleList = new People(peopleArray);
  61. IEnumerable myEnumerator = peopleList.GetMyEnumerator();
  62. Console.WriteLine("Start Iterate");
  63. foreach (Person p in myEnumerator)
  64. {
  65. Console.WriteLine(p.firstName + " " + p.lastName);
  66. }
  67.  
  68. Console.ReadLine();
  69. }
  70. }

结果:

  

转载:https://www.cnblogs.com/alongdada/p/7598119.html

C#foreach原理的更多相关文章

  1. Foreach原理

    本质:实现了一个IEnumerable接口, 01.为什么数组和集合可以使用foreach遍历? 解析:因为数组和集合都实现了IEnumerable接口,该接口中只有一个方法,GetEnumerato ...

  2. .net学习之集合、foreach原理、Hashtable、Path类、File类、Directory类、文件流FileStream类、压缩流GZipStream、拷贝大文件、序列化和反序列化

    1.集合(1)ArrayList内部存储数据的是一个object数组,创建这个类的对象的时候,这个对象里的数组的长度为0(2)调用Add方法加元素的时候,如果第一次增加元神,就会将数组的长度变为4往里 ...

  3. Array.forEach原理,仿造一个类似功能

    Array.forEach原理,仿造一个类似功能 array.forEach // 设一个arr数组 let arr = [12,45,78,165,68,124]; let sum = 0; // ...

  4. 浅析foreach原理

    在日常开发工作中,我们发现很多对象都能通过foreach来遍历,比如HashTable.Dictionary.数组等数据类型.那为何这些对象能通过foreach来遍历呢?如果写一个普通的Person类 ...

  5. C#学习笔记:foreach原理

    这篇随笔是对上一篇随笔C#关键字:yield的扩展. 关于foreach 首先,对于 foreach ,大家应该都非常熟悉,这里就简单的描述下. foreach 语句用于对实现  System.Col ...

  6. Foreach 原理

    public class Person { private string[] friends = { "asf", "ewrqwe", "ddd&qu ...

  7. C# foreach 原理以及模拟的实现

    public class Person:IEnumerable     //定义一个person类  并且 实现IEnumerable 接口  (或者不用实现此接口 直接在类 //里面写个GetEnu ...

  8. 涉及 C#的 foreach问题

    当时是用foreach实现遍历,但是函数传入参数是Object类型的,由于Objectl类型没有实现相关接口,所以foreach并不能执行. 那么下面我们来看看,想要使用foreach需要具备什么条件 ...

  9. mybatis foreach 循环 list(map)

    直接上代码: 整体需求就是: 1.分页对象里面有map map里面又有数组对象 2.分页对象里面有list list里面有map map里面有数组对象. public class Page { pri ...

随机推荐

  1. 原生js实现图片瀑布流布局,注释超详细

    完整代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF ...

  2. js实现初始化调用摄像头

    <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"% ...

  3. 透过源码看懂Flink核心框架的执行流程

    前言 Flink是大数据处理领域最近很火的一个开源的分布式.高性能的流式处理框架,其对数据的处理可以达到毫秒级别.本文以一个来自官网的WordCount例子为引,全面阐述flink的核心架构及执行流程 ...

  4. CentOS 7 Nacos 集群搭建

    环境 CentOS 7.4 MySQL 5.7 nacos-server-1.1.2 本次安装的软件全部在 /home/javateam 目录下. MySQL 安装 首先下载 rpm 安装包,地址:h ...

  5. MongoDB入门三

    MongoDB字段问题  增删查改操作 删除一列操作db.RiderReaTimePositon.update({},{$unset:{'CreateTime':''}},false,true)db. ...

  6. js基础练习题(6)

    10.其他 1.选择题 var name = 'World!'; (function () { if (typeof name === 'undefined') { var name = 'Nodei ...

  7. js事件入门(1)

    1.事件相关概念 1.1 什么是事件? 事件是用户在访问页面时执行的操作,也就是用户访问页面时的行为.当浏览器探测到一个事件时,比如鼠标点击或者按键.它可以触发与这个事件相关的JavaScript对象 ...

  8. Java中时间加减的比较

    public class TestDate{ public static void main(String[] args){try{ Date date=new Date(); DateFormat  ...

  9. API(List、Map)

    day 07 API List接口 特点: 有序,带索引,内容可以重复 Arraylist: 创建对象一般使用多态的格式: List<E> li = new ArrayList<E& ...

  10. 循环&&数组&&方法&&面向对象

    day03 数值的默认值 类型 初始化的值 byte,short,int,long 0 float,double  0.0 char 空格 boolean false 引用类型 null JVM的内存 ...