实现IEnumerable接口及理解yield关键字

 

[摘要]本文介绍实现IEnumerable接口及理解yield关键字,并讨论IEnumerable接口如何使得foreach语句可以使用。

本文讨论题目的内容。然后讨论IEnumerable接口如何使得foreach语句可以使用。之后会展示如果实现自定义的集合类,该集合类实现了IEnumerable接口。Yield关键字和遍历集合后面也讨论。

背景

一使用集合。就发现遍历集合就跟着来了。遍历集合最好的方式是实现迭代器模式-Understanding and Implementing the Iterator Pattern in C# and C++ ,C#提供foreach来以一种优雅的方式遍历。

只要集合实现了IEnumerable 接口就可以用foreach来遍历。

使用代码

首先先看一下内置的集合类如何使用foreach来遍历的。ArrayList实现了IEnumerable 接口。我们看一下:

1
2
3
4
5
6
7
8
9
10
11
12
// 看一下实现了IEnumerable 接口的集合如何遍历
ArrayList list = new ArrayList();
  
list.Add("1");
list.Add(2);
list.Add("3");
list.Add('4');
  
foreach (object s in list)
{
    Console.WriteLine(s);
}

遍历泛型集合类

Arraylist 是一个通用集合类,遍历泛型集合类也可以。因为这些泛型集合类实现了IEnumerable<T>接口,看一下吧。

1
2
3
4
5
6
7
8
9
10
11
12
// 遍历实现了IEnumerable<T>接口的泛型类
List<string> listOfStrings = new List<string>();
  
listOfStrings.Add("one");
listOfStrings.Add("two");
listOfStrings.Add("three");
listOfStrings.Add("four");
  
foreach (string s in listOfStrings)
{
    Console.WriteLine(s);
}

发现了吧。我们自定义的集合类或是泛型集合类应该实现IEnumerable和IEnumerable<T>接口。这样就可以遍历了。

理解yield关键字

在写个实现接口的例子之前,先理解一下yield关键字,yield会记录集合位置。当从一个函数返回一个值的时候,yield可以用。

如下的普通的方法。不论调用多少次,都只会返回一个return。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static int SimpleReturn()
{
    return 1;
    return 2;
    return 3;
}
  
static void Main(string[] args)
{
    // 看看
    Console.WriteLine(SimpleReturn());
    Console.WriteLine(SimpleReturn());
    Console.WriteLine(SimpleReturn());
    Console.WriteLine(SimpleReturn());
}

原因就是普通的return语句不保留函数的返回状态。每一次都是新的调用。然后返回第一个值。

但是使用下面的语句替换后就不一样。当函数第二次调用的时候。会从上次返回的地方继续调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static IEnumerable<int> YieldReturn()
{
    yield return 1;
    yield return 2;
    yield return 3;
}
static void Main(string[] args)
  
{
    // 看看yield return的效果
    foreach (int i in YieldReturn())
    {
        Console.WriteLine(i);
    }
}

显然返回1,2,3,唯一要注意的就是函数需要返回IEnumerable。,然后通过foreach调用。

在自定义的集合类里实现Ienumerable接口

现在如果我们在我们的自定义集合里定义一个方法。来迭代所有元素。然后通过使用yield返回。我们就可以成功了。

好。我们定义MyArrayList 类,实现IEnumerable 接口,该接口就会强制我们实现GetEnumerator 函数。这里我们就要使用yield了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class MyArrayList : IEnumerable
{
    object[] m_Items = null;
    int freeIndex = 0;
  
    public MyArrayList()
    {
        // 对了方便我直接用数组了,其实应该用链表
       m_Items = new object[100];
    }
  
    public void Add(object item)
    {
        // 考虑添加元素的时候
        m_Items[freeIndex] = item;
        freeIndex++;
    }
  
    // IEnumerable 函数
    public IEnumerator GetEnumerator()
   {
       foreach (object o in m_Items)
        {
           // 检查是否到了末尾。数组的话。。。没写好
            if(o == null)
            {
                break;
            }
  
            // 返回当前元素。然后前进一步
            yield return o;
        }
    }
}

之后你就可以用foreach遍历了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void Main(string[] args)
{
    //看看调用部分
    MyArrayList myList = new MyArrayList();
  
    myList.Add("1");
    myList.Add(2);
    myList.Add("3");
    myList.Add('4');
  
    foreach (object s in myList)
    {
        Console.WriteLine(s);
    }
}

这个类啊。没写好。也不完整。只要是让你理解。。模拟一下而已。

自定义泛型类里实现Ienumerable<T>接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class MyList<T> : IEnumerable<T>
{
    T[] m_Items = null;
    int freeIndex = 0;
  
    public MyList()
    {
        // 为了方便。使用数组
        m_Items = new T[100];
    }
  
    public void Add(T item)
    {
        //添加元素
        m_Items[freeIndex] = item;
        freeIndex++;
    }
  
    #region IEnumerable<T> Members 
  
    public IEnumerator<T> GetEnumerator()
    {
        foreach (T t in m_Items)
        {
            //检查是否到了末尾。数组的话。。。没写好
            if (t == null) // 如果T不是一个可空类型。就中断
            {
                break;
            }
  
            // 返回当前元素,然后前进一步
            yield return t;
        }
    }
  
    #endregion
  
     #region IEnumerable Members
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        // 此处调用泛型版本
        return this.GetEnumerator();
    }
  
    #endregion
}

之后就可以使用foreach了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void Main(string[] args)
{
    // 使用示例
    MyList<string> myListOfStrings = new MyList<string>();
  
    myListOfStrings.Add("one");
    myListOfStrings.Add("two");
    myListOfStrings.Add("three");
    myListOfStrings.Add("four");
  
    foreach (string s in myListOfStrings)
    {
        Console.WriteLine(s);
    }
}

转载yield关键字理解的更多相关文章

  1. C#中yield关键字理解

    yield关键字之前用得较少,但是在做项目开发的过程中也遇到了,当时有点迷惑,就顺便研究学习了一下,以下是个人理解,不到之处欢迎拍砖!废话就到这,上代码: class Program { static ...

  2. [原译]实现IEnumerable接口&理解yield关键字

    原文:[原译]实现IEnumerable接口&理解yield关键字 著作权声明:本文由http://leaver.me 翻译,欢迎转载分享.请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢 ...

  3. 深入理解python中的yield关键字

    想必大家都看过这样的代码: 上面的这段代码会计算0-9的平方并打印出来. 那么问题来了,这段代码和我们要说的东西有什么区别呢? 这里的关键字,yield,我在前面的文章里已经发过了.那么yield是什 ...

  4. 理解 ES6 语法中 yield 关键字的返回值

    在 ES6 中新增了生成器函数的语法,本文解释了生成器函数内 yield 关键字的返回值. 描述 根据语法规范,yield 关键字用来暂停和继续执行一个生成器函数.当外部调用生成器的 next() 方 ...

  5. 理解 ES6 语法中 yield* 关键字的作用

    在 ES6 中新增了生成器函数的语法,本文解释了与生成器函数有关的 yield* 关键字,及其使用场景. 描述 根据语法规范,yield* 的作用是代理 yield 表达式,将需要函数本身产生(yie ...

  6. C# 基础小知识之yield 关键字 语法糖

    原文地址:http://www.cnblogs.com/santian/p/4389675.html 对于yield关键字我们首先看一下msdn的解释: 如果你在语句中使用 yield 关键字,则意味 ...

  7. C# 基础小知识之yield 关键字

    对于yield关键字我们首先看一下msdn的解释: 如果你在语句中使用 yield 关键字,则意味着它在其中出现的方法.运算符或 get 访问器是迭代器. 通过使用 yield 定义迭代器,可在实现自 ...

  8. 解析Python中的yield关键字

    前言 python中有一个非常有用的语法叫做生成器,所利用到的关键字就是yield.有效利用生成器这个工具可以有效地节约系统资源,避免不必要的内存占用. 一段代码 def fun(): for i i ...

  9. python yield 关键字

    最近看代码看到python里面的yield关键字,和我之前接触的语言好像都没有来着,我就查了查它的含义,大概理解如下: >>> def createGenerator(): ... ...

随机推荐

  1. BZOJ_1030_[JSOI2007]_文本生成器_(AC自动机+DP)

    描述 http://www.lydsy.com/JudgeOnline/problem.php?id=1030 给出一些单词,问长度为\(m\)的文章有多少文章中出现过任意一个或多个单词. 分析 文章 ...

  2. Ajax、Comet与Websocket

    从 http 协议说起 1996年IETF  HTTP工作组发布了HTTP协议的1.0版本 ,到现在普遍使用的版本1.1,HTTP协议经历了17 年的发展.这种分布式.无状态.基于TCP的请求/响应式 ...

  3. Hadoop configration类分析

    configration这个类是分析hadoop源代码一个很好地入口. 先从需求说起.对于一个大型的文件系统,基于配置文件可以增强灵活性.congfigration类就是为了管理配置文件的. 配置文件 ...

  4. codeforces 333B - Chips

    注意:横向纵向交叉时,只要两条边不是正中的边(当n&1!=1),就可以余下两个chip. 代码里数组a[][]第二维下标 0表示横向边,1表示纵向边. #include<stdio.h& ...

  5. ECshop 二次开发模板教程2

    不知道大家是学会用循环了呢,还是我的言语实在有问题,大家实在无法完成阅读哦,居然大家都没有问题,暂时心里安慰,把他当做好事情,大家都会调用了,呵呵,那我们继续循环调用商品了!好,继续在我们昨天的基础上 ...

  6. 22个所见即所得在线 Web 编辑器

    前言: 关于编辑器,适合的才是最好的,接下来,我会写一些关于日志编辑器的文章,今天就写写,可能内容会比较多. --------------------------------------------- ...

  7. Dapper.net 在Parameterized时对于String的扩展(转)

    虽然Dapper通过提供的DbString本身支持对于String的指定Parameterized,但这方法明显不够,当Insert时,我们更希望是把一个Poco直接传递过去,而不是来new一个匿名函 ...

  8. Most Powerful(ZOJ 3471状压dp)

    题意:n个原子,两两相撞其中一个消失,产生能量,给出任意两原子相撞能产生的能量,求能产生的最大能量. 分析:dp[i]表示情况为i时产生的最大能量 /*#include <map> #in ...

  9. 接入脚本interface.php实现代码

    承接上文的WeChatCallBack 在WeChatCallBack类的成员变量中定义了各种消息都会有的字段,这些字段在init函数中赋值.同时也把解析到的XML对象作为这个类的成员变量$_post ...

  10. 【Windows核心编程】重载类成员函数new / new[] / delete / delete[]

    // Heap.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <Windows.h> #include &l ...