C#中foreach实现原理

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

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

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

实际此代码的执行过程:

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

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

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

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

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

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

    // 摘要:
    //     支持对非泛型集合的简单迭代。
    public interface IEnumerator
    {
        // 摘要:
        //     获取集合中的当前元素。
        //
        // 返回结果:
        //     集合中的当前元素。
        //
        // 异常:
        //   System.InvalidOperationException:
        //     枚举数定位在该集合的第一个元素之前或最后一个元素之后。
        object Current { get; }

        // 摘要:
        //     将枚举数推进到集合的下一个元素。
        //
        // 返回结果:
        //     如果枚举数成功地推进到下一个元素,则为 true;如果枚举数越过集合的结尾,则为 false。
        //
        // 异常:
        //   System.InvalidOperationException:
        //     在创建了枚举数后集合被修改了。
        bool MoveNext();
        //
        // 摘要:
        //     将枚举数设置为其初始位置,该位置位于集合中第一个元素之前。
        //
        // 异常:
        //   System.InvalidOperationException:
        //     在创建了枚举数后集合被修改了。
        void Reset();
    }

以下是自定义一个迭代器的示例(https://msdn.microsoft.com/en-us/library/system.collections.ienumerator.aspx):

using System;
using System.Collections;

// Simple business object.
public class Person
{
    public Person(string fName, string lName)
    {
        this.firstName = fName;
        this.lastName = lName;
    }

    public string firstName;
    public string lastName;
}

// Collection of Person objects. This class
// implements IEnumerable so that it can be used
// with ForEach syntax.
public class People : IEnumerable
{
    private Person[] _people;
    public People(Person[] pArray)
    {
        _people = new Person[pArray.Length];

        for (int i = 0; i < pArray.Length; i++)
        {
            _people[i] = pArray[i];
        }
    }

// Implementation for the GetEnumerator method.
    IEnumerator IEnumerable.GetEnumerator()
    {
       return (IEnumerator) GetEnumerator();
    }

    public PeopleEnum GetEnumerator()
    {
        return new PeopleEnum(_people);
    }
}

// When you implement IEnumerable, you must also implement IEnumerator.
public class PeopleEnum : IEnumerator
{
    public Person[] _people;

    // Enumerators are positioned before the first element
    // until the first MoveNext() call.
    int position = -1;

    public PeopleEnum(Person[] list)
    {
        _people = list;
    }

    public bool MoveNext()
    {
        position++;
        return (position < _people.Length);
    }

    public void Reset()
    {
        position = -1;
    }

    object IEnumerator.Current
    {
        get
        {
            return Current;
        }
    }

    public Person Current
    {
        get
        {
            try
            {
                return _people[position];
            }
            catch (IndexOutOfRangeException)
            {
                throw new InvalidOperationException();
            }
        }
    }
}

class App
{
    static void Main()
    {
        Person[] peopleArray = new Person[3]
        {
            new Person("John", "Smith"),
            new Person("Jim", "Johnson"),
            new Person("Sue", "Rabon"),
        };

        People peopleList = new People(peopleArray);
        foreach (Person p in peopleList)
            Console.WriteLine(p.firstName + " " + p.lastName);

    }
}

/* This code produces output similar to the following:
 *
 * John Smith
 * Jim Johnson
 * Sue Rabon
 *
 */

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

using System;
using System.Collections;

// Simple business object.
public class Person
{
    public Person(string fName, string lName)
    {
        this.firstName = fName;
        this.lastName = lName;
    }

    public string firstName;
    public string lastName;
}

// Collection of Person objects. This class
// implements IEnumerable so that it can be used
// with ForEach syntax.
public class People : IEnumerable
{
    private Person[] _people;

    public People(Person[] pArray)
    {
        _people = new Person[pArray.Length];

        for (int i = 0; i < pArray.Length; i++)
        {
            _people[i] = pArray[i];
        }
    }

    // Implementation for the GetEnumerator method.
    IEnumerator IEnumerable.GetEnumerator()
    {
        for (int i = 0; i < _people.Length; i++)
        {
            yield return _people[i];
        }
    }

}

class App
{
    static void Main()
    {
        Person[] peopleArray = new Person[3]
        {
            new Person("John", "Smith"),
            new Person("Jim", "Johnson"),
            new Person("Sue", "Rabon"),
        };

        People peopleList = new People(peopleArray);
        foreach (Person p in peopleList)
            Console.WriteLine(p.firstName + " " + p.lastName);
    }
}

  

时间: 2024-10-12 23:39:40

C#中foreach实现原理的相关文章

从字节码看Java中for-each循环(增强for循环)实现原理

转发:http://blog.csdn.net/u011392897/article/details/54562596 for-each循环是jdk1.5引入的新的语法功能.并不是所有东西都可以使用这个循环的.可以看下Iterable接口的注释,它说明了除了数组外,其他类想要使用for-each循环必须实现这个接口.这一点表明除了数组外的for-each可能底层是由迭代器实现的. Iterable接口在1.8之前只有一个方法,Iterator<T> iterator(),此方法返回一个迭代器.

Java语法糖1:可变长度参数以及foreach循环原理

语法糖 接下来几篇文章要开启一个Java语法糖系列,所以首先讲讲什么是语法糖.语法糖是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的字节码或者特定的方式对这些语法做一些处理,开发者就可以直接方便地使用了.这些语法糖虽然不会提供实质性的功能改进,但是它们或能提高性能.或能提升语法的严谨性.或能减少编码出错的机会.Java提供给了用户大量的语法糖,比如泛型.自动装箱.自动拆箱.foreach循环.变长参数.内部类.枚举类.断言(as

图像处理中的数学原理详解17——卷积定理及其证明

欢迎关注我的博客专栏"图像处理中的数学原理详解" 全文目录请见 图像处理中的数学原理详解(总纲) http://blog.csdn.net/baimafujinji/article/details/48467225 图像处理中的数学原理详解(已发布的部分链接整理) http://blog.csdn.net/baimafujinji/article/details/48751037 1.4.5   卷积定理及其证明 卷积定理是傅立叶变换满足的一个重要性质.卷积定理指出,函数卷积的傅立叶变

分布式系统中的CAP原理

分布式系统中的CAP原理,布布扣,bubuko.com

asp.net中session的原理及应用

Session简介丶特性 1.Session是一种Web会话中的常用状态之一. 2.Session提供了一种把信息保存在服务器内存中的方式.他能储存任何数据类型,包含自定义对象. 3.每个客户端的Seesion是独立存储的. 4.在整个会话过程中,只要SessionID的cookie不丢失,都会保存Session信息的. 5.Session不能跨进程访问,只能由该会话的用户访问.应为提取Session数据的id标识是以Cookie的方式保存到访问者浏览器的缓存里的. 6.当会话终止,或过期时,服

Java中forEach, 用来遍历数组

这里的for是Java中forEach, 用来遍历数组的.for(int i : d) 就是遍历int型数组d的 每一次访问数组d的时候读取的数据放入int型的i中.和for(int i=0;i<d.length();i++)是一样的,但是forEach的可用场合较多. public class e1 {public static void main(String[] args){ int[]d=new int[] {1,2,3,4,64,1234,3124,657,22}; System.ou

linux中mmap系统调用原理分析与实现

参考文章:http://blog.csdn.net/shaoguangleo/article/details/5822110 linux中mmap系统调用原理分析与实现 1.mmap系统调用(功能)      void* mmap ( void * addr , size_t len , int prot , int flags ,int fd , off_t offset )      内存映射函数mmap, 负责把文件内容映射到进程的虚拟内存空间, 通过对这段内存的读取和修改,来实现对文件的

c++中的vector原理

vectorvector就是动态数组.它也是在堆中分配内存,元素连续存放,有保留内存,如果减少大小后,内存也不会释放.如果新值>当前大小时才会再分配内存. 它拥有一段连续的内存空间,并且起始地址不变,因此它能非常好的支持随即存取,即[]操作符,但由于它的内存空间是连续的,所以在中间进行插入和删除会造成内存块的拷贝,另外,当该数组后的内存空间不够时,需要重新申请一块足够大的内存并进行内存的拷贝.这些都大大影响了vector的效率.对最后元素操作最快(在后面添加删除最快 ), 此时一般不需要移动内存

word2vec 中的数学原理详解

word2vec 中的数学原理详解 word2vec 是 Google 于 2013 年开源推出的一个用于获取 word vector 的工具包,它简单.高效,因此引起了很多人的关注.由于 word2vec 的作者 Tomas Mikolov 在两篇相关的论文 [3,4] 中并没有谈及太多算法细节,因而在一定程度上增加了这个工具包的神秘感.一些按捺不住的人于是选择了通过解剖源代码的方式来一窥究竟. 第一次接触 word2vec 是 2013 年的 10 月份,当时读了复旦大学郑骁庆老师发表的论文