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
版权声明:本文为博主原创文章,转载请附上博文链接!