C#中foreach以及unity协程背后的秘密

1.对于foreach来说我们并不陌生,我们经常要使用它来遍历集合,或者是对象中的集合,那么下面就来介绍一个遍历对象中集合的例子,然后借此解开foreach遍历背后的神秘面纱。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ForeachTest
{
    public class Token : IEnumerable
    {
        public  string[] elements;
        public Token(string source,char[] delimiters)
        {
            elements = source.Split(delimiters);
        }
        public IEnumerator GetEnumerator()
        {
            return new TokenEnumerator(this);
        }
        //public IEnumerator GetEnumerator()
       // {
         //   for (int i = 0; i <this.elements.Length; i++)
        //    {
          //      yield return elements[i];
        //    }
       // }
    }
    public class TokenEnumerator : IEnumerator
    {
        private int position = -1;
        private Token t;
        public TokenEnumerator(Token t)
        {
            this.t = t;
        }
        public bool MoveNext()
        {
            if (position < t.elements.Length - 1)
            {
                position++;
                return true;
            }
            else
            {
                return false;
            }
        }
        public void Reset()
        {
            position = -1;
        }
        public object Current
        {
            get
            {
                return t.elements[position];
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Token f = new Token("This is a well-done program", new char[] { ' ', '-' });
            foreach (string item in f)
            {
                Console.WriteLine(item);
            }
            //IEnumerator numerator = f.GetEnumerator();
            //while (numerator.MoveNext())
           // {
             //   Console.WriteLine(numerator.Current);
           // }
            //Console.ReadKey();
        }
    }
}


在这个例子中创建了两个类,Token,和TokenEnumerator他们两个分别实现了IEnumerable,和IEnumerator两个接口,在IEnumerator接口中,需实现public IEnumerator GetEnumerator()方法,该方法的目的是要返回一个枚举器,观察其中的代码可以发现,他返回的是TokenEnumerator类的实例,其实也就侧面说明该类是实现集合的枚举功能。在该类中实现了MoveNext()方法,Reset()方法(主要功能是将枚举指针置为-1,这个-1其实是有道理的后面会讲到),Current属性(是获取当前的值)。
接下来说他的执行过程,在主函数中创建Token的实例t,然后直接用foreach语句进行遍历,运行就可以得到结果;

不知道此时读者有没有发现,其中哪里与我们平时使用的foreach不一样。没错,就是遍历对象不一样,我们平时在遍历的时候都是直接将一个集合对象当做遍历对象的,(集合可以使用索引进行直接获取,他是按序排列的),而此处却是以一个类实例作为遍历对象。

此时读者可能会感到疑惑,为什么这里就可以直接用对象进行遍历呢,原因是,我们在这个类中让他实现了IEnumerator和IEnumerable接口,这样这个实例就具有了迭代器的功能了。好了,说到这,还没讲到,这个foreach到底是如何遍历这个实例的,接下来开始步入关键点介绍。

其实foreach并不是像for结构那样的简单模式,他是经过封装的一个迭代器形式,他的具体执行过程经过反编译可以看到是这样的:

IEnumerator enumerator = new Token("This is a well-done program", delimiters).GetEnumerator();
    while (enumerator.MoveNext())
    {
        Console.WriteLine(enumerator.Current);
    }


执行步骤如下:
1)创建类实例,并调用类实例中实现的GetEnumerator()方法,以便获得枚举器,
2)接下来进行循环操作,调用MoveNext()方法是枚举指针从-1前进到0。获得返回值,进行判断,若为true就进入循环体,若为false就结束循环
3)调用枚举器中的Current属性显示结果。
如上便是迭代器的执行过程,可以发现他与for循环是有很大的差别的。

大家想想一下,若是每次想要遍历时都要做如此复杂的操作是不是很麻烦,所以引入了yield关键字,来简化这个迭代器的书写过程;引入Yield关键字之后,那么这个过程的就不需要再手动创建枚举器那个类了,编译器在编译时会自动生成那个过程,同时要遍历的这个类也不需要再继承IEnumerable接口了。
上面代码中也有Yield关键字的实现方法。

下面再介绍一种简单的例子,我们再通过反编译来查看他的具体执行过程:

class Test
    {
        static IEnumerator<int> TestIterater()
        {
            for (int i = 0; i <10; i++)
            {
                yield return i;
            }
        }
    }

可以发现是一个非常简单的过程,简化了大量代码,但是这样也有一个问题,这些代码编译器到底是如何执行的呢,接下来我们来看他的反编译代码,看看编译器到底对这段代码做了什么样的处理:

internal class Test
{
   // Methods
   public Test();
   [IteratorStateMachine(typeof(<TestIterater>d__0))]
   private static IEnumerator<int> TestIterater();

   // Nested Types
   [CompilerGenerated]
   private sealed class <TestIterater>d__0 : IEnumerator<int>, IDisposable, IEnumerator
   {
       // Fields
       private int <>1__state;
       private int <>2__current;
       private int <i>5__1;

       // Methods
       [DebuggerHidden]
       public <TestIterater>d__0(int <>1__state);
       private bool MoveNext();
       [DebuggerHidden]
       void IEnumerator.Reset();
       [DebuggerHidden]
       void IDisposable.Dispose();

       // Properties
       int IEnumerator<int>.Current { [DebuggerHidden] get; }
       object IEnumerator.Current { [DebuggerHidden] get; }
   }
}



上述代码反编译成了如上形式,这个过程比较复杂,但不妨碍我们阅读,我们只用关心的是static IEnumerator<int> TestIterater()方法的编译,以及其中Yield关键字的编译,这个方法的编译结果如下:

[IteratorStateMachine(typeof(<TestIterater>d__0))]
private static IEnumerator<int> TestIterater()
{
   this.<i>5__1 = 0;
   while (this.<i>5__1 < 10)
   {
       yield return this.<i>5__1;
       int num2 = this.<i>5__1;
       this.<i>5__1 = num2 + 1;
   }
}

在上述代码中有不认识的变量名,那是因为编译过程中还产生了一个新的类如下,编译器在编译时生成这段代码,所以为了防止与程序中的代码同名,所以命名为这种方式:

 [CompilerGenerated]
private sealed class <TestIterater>d__0 : IEnumerator<int>, IDisposable, IEnumerator
{
    // Fields
    private int <>1__state;
    private int <>2__current;
    private int <i>5__1;

    // Methods
    [DebuggerHidden]
    public <TestIterater>d__0(int <>1__state)
    {
        this.<>1__state = <>1__state;
    }

    private bool MoveNext()
    {
        switch (this.<>1__state)
        {
            case 0:
                this.<>1__state = -1;
                this.<i>5__1 = 0;
                while (this.<i>5__1 < 10)
                {
                    this.<>2__current = this.<i>5__1;
                    this.<>1__state = 1;
                    return true;
                Label_003F:
                    this.<>1__state = -1;
                    int num2 = this.<i>5__1;
                    this.<i>5__1 = num2 + 1;
                }
                return false;

            case 1:
                goto Label_003F;
        }
        return false;
    }

    [DebuggerHidden]
    void IEnumerator.Reset()
    {
        throw new NotSupportedException();
    }

    [DebuggerHidden]
    void IDisposable.Dispose()
    {
    }

    // Properties
    int IEnumerator<int>.Current =>
        this.<>2__current;

    object IEnumerator.Current =>
        this.<>2__current;
}

观察这个类发现,其中其实是实现了MoveNext(),Reset()方法和Current属性,这几个重要的方法,故而可以知道,继续按照foreach的遍历方式就可以达到遍历集合中数据的效果。当你按照上面的方式走一遍这个遍历过程,你会发现,MoveNext()方法是在1和2这两种状态下进行跳转执行的,那么此时再考虑为什么程序可在Yield return语句之后接着执行,正如编译代码中显示的那样,是因为goto语句实现了半路插入。

在最上面我曾提到过-1的问题,下面就来说说它。
根据Ecma-334标准也就是C#语言标准的第26.2(Enumerator objects)小节
可以知道迭代器有四种可能的状态,即Before状态,Running状态,Suspended状态,After状态,而其中Before状态是作为初始状态出现的。
而反编译中和上面提到的那个-1对应的状态就是Running状态,表明该状态机正在执行,当然也会用于After状态,
0状态即为Before状态,表明MoveNext()一次都没有调用呢。
那些正整数状态主要用来标示当遇到Yield关键字之后,代码从哪里恢复。

2.迭代器部分说完了,下面来说说协程,
有了上面对迭代器的认识,那么协程的理解就简单多了,
看下面的例子:

class Test1
    {
        static IEnumerator TestDanXiang()
        {
            yield return new Test3();
            Console.WriteLine("测试");
        }
    }
    class Test3
    {
        int b = 2;
           
    }

这个例子不是协程,但是可以发现他的形式是跟u3d中的协程是一样的,无非是yield return后面的对象为www这类的,在加上一个StartCoroutine()方法开启这个协程。其实执行的机理是相同的。看一下这段代码的反编译程序:

internal class Test1
{
    // Methods
    [IteratorStateMachine(typeof(<TestDanXiang>d__0))]
    private static IEnumerator TestDanXiang()
    {
        yield return new Test3();
    }

    // Nested Types
    [CompilerGenerated]
    private sealed class <TestDanXiang>d__0 : IEnumerator<object>, IDisposable, IEnumerator
    {
        // Fields
        private int <>1__state;
        private object <>2__current;

        // Methods
        [DebuggerHidden]
        public <TestDanXiang>d__0(int <>1__state)
        {
            this.<>1__state = <>1__state;
        }

        private bool MoveNext()
        {
            switch (this.<>1__state)
            {
                case 0:
                    this.<>1__state = -1;
                    this.<>2__current = new Test3();//这个地方就是Yield return 后的对象
                    this.<>1__state = 1;
                    return true;

                case 1:
                    this.<>1__state = -1;
                    return false;
            }
            return false;
        }

        [DebuggerHidden]
        void IEnumerator.Reset()
        {
            throw new NotSupportedException();
        }

        [DebuggerHidden]
        void IDisposable.Dispose()
        {
        }

        // Properties
        object IEnumerator<object>.Current =>
            this.<>2__current;

        object IEnumerator.Current =>
            this.<>2__current;
    }
}



观察者个反编译代码,可以发现他其实与普通的迭代方式是相同的,如此,就可以理解协程的执行机理了
--------------------- 
作者:feiyuezouni 
来源:CSDN 
原文:https://blog.csdn.net/feiyuezouni/article/details/84777675 
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值