• 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的,多出的一些预料之外的影响也会把我们带到坑里;我觉得比较有用的使用情况是:多线程批量处理的时候,获取到一个数据便调用线程处理,一边处理一边获取新数据,相对于获取到所有数据在分配给线程处理是可以提高性能,特别是获取数据需要耗时的情况。

  • 相关阅读:
    git 提示error setting certificate verify locations 解决方案
    检查性异常和非检查性异常
    Intellij IDEA 代码格式化/保存时自动格式化
    IntelliJ IDEA 如何设置类头注释和方法注释
    IntelliJ IDEA 创建 Java包
    python列表的增删改查用法
    我的第一篇博客
    Python全局变量和局部变量相关知识点
    学生管理系统(改进版)
    Python---函数的相关知识点总结一:
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/5602372.html
Copyright © 2020-2023  润新知