C# 使用IENUMERABLE,YIELD

C# 使用IENUMERABLE,YIELD

前言

上篇文章中我得出结论,遍历迭代器修改迭代器中项目的值未生效,是因为使用了yield return,并且每次遍历迭代器都执行返回迭代器的方法。这篇文章是接着上篇文章,从代码实现的角度来验证出现这种情况的原因。
首先介绍下一种查看代码实现的一种方法:使用Reflector反编译dll或者exe文件我们可以看到里面的代码,在下面的配置中可以选择代码实现的C#版本:
Tools->Options

这里我们选择为None,这时Reflector将不会对反编译的代码进行优化,将最原始的实现方法展现给我们,我们在这种代码中可以看到很多C#相对底层的实现。

正文

下面是我们要查看的代码,一个是获取迭代器返回多个ListTest类的示例,另一个是对迭代器进行多次循环并修改代器内容:

public void YieldTest()
{
    var list = GetEnumerable();
    for (int i = 0; i < 100; i++)
    {
        foreach (var test in list)
        {
            test.atr1 = 0;
            test.atr2 = "11";
        }
    }
}

public IEnumerable<ListTest> GetEnumerable()
{
    for (int i = 0; i < 2; i++)
    {
        yield return new ListTest()
        {
            atr1 = i + 1,
            atr2 = string.Format("test{0}", i + 1)
        };
    }
}

下面是这两段代码反编译的结果:

public void YieldTest()
{
    IEnumerable<ListTest> enumerable;
    int num;
    ListTest test;
    IEnumerator<ListTest> enumerator;
    bool flag;
    enumerable = this.GetEnumerable();
    num = 0;
    goto Label_005A;
Label_000C:
    enumerator = enumerable.GetEnumerator();
Label_0015:
    try
    {
        goto Label_0034;
    Label_0017:
        test = enumerator.Current;
        test.atr1 = 0;
        test.atr2 = "11";
    Label_0034:
        if (enumerator.MoveNext() != null)
        {
            goto Label_0017;
        }
        goto Label_0054;
    }
    finally
    {
    Label_0042:
        if ((enumerator == null) != null)
        {
            goto Label_0053;
        }
        enumerator.Dispose();
    Label_0053:;
    }
Label_0054:
    num += 1;
Label_005A:
    if ((num < 100) != null)
    {
        goto Label_000C;
    }
    return;
}

  public void YieldTest()
{
    IEnumerable<ListTest> enumerable;
    int num;
    ListTest test;
    IEnumerator<ListTest> enumerator;
    bool flag;
    enumerable = this.GetEnumerable();
    num = 0;
    goto Label_005A;
Label_000C:
    enumerator = enumerable.GetEnumerator();
Label_0015:
    try
    {
        goto Label_0034;
    Label_0017:
        test = enumerator.Current;
        test.atr1 = 0;
        test.atr2 = "11";
    Label_0034:
        if (enumerator.MoveNext() != null)
        {
            goto Label_0017;
        }
        goto Label_0054;
    }
    finally
    {
    Label_0042:
        if ((enumerator == null) != null)
        {
            goto Label_0053;
        }
        enumerator.Dispose();
    Label_0053:;
    }
Label_0054:
    num += 1;
Label_005A:
    if ((num < 100) != null)
    {
        goto Label_000C;
    }
    return;
}

public IEnumerable<ListTest> GetEnumerable()
{
    <GetEnumerable>d__12 d__;
    IEnumerable<ListTest> enumerable;
    d__ = new <GetEnumerable>d__10(-2);
    d__.<>4__this = this;
    enumerable = d__;
Label_0013:
    return enumerable;
}

首先我们看下YieldTest函数的代码,变长了很多,其实理清楚里面goto语句的话,逻辑还是很清晰的,这里我们看出下面几个点:

  • for循环是通过判断步进值num和使用goto语句来实现的。
  • foreach关键字的实现逻辑是:使用迭代器的Current属性获取当前项执行操作,然后调用MoveNext()方法使Current属性指向下一项,然后goto语句循环处理。

再来看GetEnumerable()方法,这里就比较奇怪了,代码返回了一个<GetEnumerable>d__10类的实例,并没有我函数中的代码逻辑,而且我代码中也没有这个类,这个类是.net为我们自动生成的,并且实现了迭代器接口:

YieldTest函数中便使用了这个迭代器,迭代器的Current属性便是我们代码中返回的ListTest类,而我代码的逻辑其实在MoveNext()方法中:

private bool MoveNext()
{
    bool flag;
    int num;
    bool flag2;
    num = this.<>1__state;
    switch (num)
    {
        case 0:
            goto Label_0019;

        case 1:
            goto Label_0017;
    }
    goto Label_001B;
Label_0017:
    goto Label_008B;
Label_0019:
    goto Label_0020;
Label_001B:
    goto Label_00AF;
Label_0020:
    this.<>1__state = -1;
    this.<i>5__11 = 0;
    goto Label_00A1;
Label_0031:
    this.<>g__initLocalf = new ListTest();
    this.<>g__initLocalf.atr1 = this.<i>5__11 + 1;
    this.<>g__initLocalf.atr2 = string.Format("test{0}", (int) (this.<i>5__11 + 1));
    this.<>2__current = this.<>g__initLocalf;
    this.<>1__state = 1;
    flag = 1;
    goto Label_00B3;
Label_008B:
    this.<>1__state = -1;
    this.<i>5__11 += 1;
Label_00A1:
    if ((this.<i>5__11 < 2) != null)
    {
        goto Label_0031;
    }
Label_00AF:
    flag = 0;
Label_00B3:
    return flag;
}

到这里我们便可以理解本文开头的两个问题了:
1、使用yield return时,在foreach中修改迭代器的内容不生效:

调用yield return的方法时只是返回了一个迭代器的实例,并没有真正执行方法里的逻辑,当我们循环迭代器调用MoveNext()方法时,才会真正执行我们写代码逻辑,而且每次循环迭代器都会执行MoveNext()方法获取新的实例,所以每次操作都不会影响到下一次的循环。

2、每次循环迭代器都会执行GetEnumerable()函数:

因为每次执行的是MoveNext()方法,而原本GetEnumerable()中的代码已经在MoveNext()方法中了。

下面是我对yield的一些思考:

就正常需求来说是没有必要使用yield的,多出的一些预料之外的影响也会把我们带到坑里;我觉得比较有用的使用情况是:多线程批量处理的时候,获取到一个数据便调用线程处理,一边处理一边获取新数据,相对于获取到所有数据在分配给线程处理是可以提高性能,特别是获取数据需要耗时的情况。

时间: 2024-11-06 23:02:06

C# 使用IENUMERABLE,YIELD的相关文章

IEnumerator/ IEnumerable/ yield return/ StartCoroutine 详解

IEnumerator/ IEnumerable public interface IEnumerable { IEnumerator GetEnumerator(); } public interface IEnumerator { bool MoveNext(); void Reset(); Object Current { get; } } 在两者的使用上,有下面几点需要注意 1.一个Collection要支持foreach方式的遍历,必须实现IEnumerable接口(亦即,必须以某种方

yield return 和 yield break

//yield return 返回类型必须为 IEnumerable.IEnumerable<T>.IEnumerator 或 IEnumerator<T>. static IEnumerator<int> yieldTest() //yield return 返回IEnumerator  { yield return 1; yield return 4; if (true)//如果为True 输出 1,4;//如果是False 输出 1,4,3,2 { yield b

C#基础复习IEnumerable(和 yield return的使用滴呀 )

IEnumerable 真是基础中的基础,然而..... 我们直接来看看这个接口的实现吧: 它是一个公开枚举数,该枚举数支持在非泛型集合上进行简单的迭代.换句话说,对于所有数组的遍历,都来自IEnumerable,那么我们就可以利用这个特性,来定义一个能够遍历xxxxxx的通用方法 先看我们的经典实例1: using System; using System.Collections; using System.Collections.Generic; using System.Linq; usi

C# 使用IEnumerable,yield 返回结果,同时使用foreach时,在循环内修改变量的值无效

在项目中遇到了一个比较奇怪的问题,在foreach循环中修改列表的值后没有生效,后面使用时还是获取列表时的值,原因是因为使用了 yield return .下面让我们来探究下其中的原因: 首先来看下 yield return 官方的解释 使用 yield return 语句可一次返回一个元素.通过 foreach 语句或 LINQ 查询来使用迭代器方法. foreach 循环的每次迭代都会调用迭代器方法. 迭代器方法运行到 yield return 语句时,会返回一个 expression,并保

【Unity3D/C#】利用IEnumerable&lt;&gt;和yield产生斐波那契数列

using UnityEngine; using System.Collections; using System.Collections.Generic; public class YieldTest : MonoBehaviour { public bool b = true; public IEnumerator myIEnumertor = null; void Awake() { } // Use this for initialization void Start () { myIE

c#yield,IEnumerable,IEnumerator

foreach 在编译成IL后,实际代码如下: 即:foreach实际上是先调用可枚举对象的GetEnumerator方法,得到一个Enumerator对象,然后对Enumerator进行while循环的相关操作,然后得到可枚举对象中的每一个值. 可以把可枚举对象中的所有值想像成一个链表,Enumerator是链表的指针,Enumerator.Current是当前指向的元素,Enumerator.MoveNext是指针后移.于是用while循环便可以用类似遍历链表的方式得到对象中的所有值. 一个

从yield关键字看IEnumerable和Collection的区别

C#的yield关键字由来以久,如果我没有记错的话,应该是在C# 2.0中被引入的.相信大家此关键字的用法已经了然于胸,很多人也了解yield背后的“延迟赋值”机制.但是即使你知道这个机制,你也很容易在不经意间掉入它制造的陷阱. 目录 一.一个很简单的例子 二.简单谈谈“延迟赋值” 三.从反射的代码帮助我们更加直接的了解yield导致的延迟赋值 四.如果需要“立即赋值”怎么办? 后记 一.一个很简单的例子 下面是一个很简单的例子:Vector为自定义表示二维向量的类型,Program的静态方法G

IEnumerable、IEnumerator与yield的学习

我们知道数组对象可以使用foreach迭代进行遍历,同时我们发现类ArrayList和List也可以使用foreach进行迭代.如果我们自己编写的类也需要使用foreach进行迭代时该怎么办呢? IEnumerable: 1 public interface IEnumerable 2 { 3 IEnumerator GetEnumerator(); 4 } 如果自己编写的类需要foreach进行迭代就需要实现IEnumerable接口,表示当前的类可以进行迭代. 我们发现该接口唯一的方法返回的

C# yield return用法

本文实例讲述了C#中yield return用法,并且对比了使用yield return与不使用yield return的情况,以便读者更好的进行理解.具体如下: yield关键字用于遍历循环中,yield return用于返回IEnumerable<T>,yield break用于终止循环遍历. 有这样的一个int类型的集合: ? 1 2 3 4 static List<int> GetInitialData() {   return new List<int>(){