zoukankan      html  css  js  c++  java
  • C#迭代器——由foreach说开去

    原文链接:https://blog.csdn.net/u013477973/article/details/65635737

    C#迭代器——由foreach说开去
    foreach在数组集合的遍历时会被经常用到,例如:

    string[] strs = new string[] { "red", "green","blue" };
    foreach (var item in strs)
    {
    Console.WriteLine(item);
    }
    1
    2
    3
    4
    5
    使用foreach做遍历确实很方便,然而并不是每一种类型都能使用foreach进行遍历操作,只有实现了IEnumerable接口的类(也叫做可枚举类型)才能进行foreach的遍历操作,集合和数组已经实现了这个接口,所以能进行foreach的遍历操作

    IEnumerable和IEnumerator
    IEnumerable叫做可枚举接口,它的成员只有一个

    GetEnumerator()
    返回一个枚举器对象,即实现了IEnumerator接口的类的实例,实现IEnumerator接口的枚举器包含3个函数成员:

    Current属性
    MoveNext()方法
    Reset()方法
    Current属性为只读属性,返回枚举序列中的当前位置,MoveNext()把枚举器的位置前进到下一项,返回布尔值,新的位置若是有效的,返回true,否则返回false,ReSet()将位置重置为原始状态。
    举个例子,实现自己的可枚举类型,先实现IEnumerator枚举器类型:

    class Enumerator1<T> : IEnumerator<T> {
    private int _position = -1;
    private T[] t;
    public Enumerator1(T[] a) {
    t = new T[a.Length];
    for (int i = 0; i < t.Length; i++) {
    t[i] = a[i];
    }
    }
    public T Current
    {
    get
    {
    if (_position == -1) {
    throw new InvalidOperationException();
    }
    return t[_position];
    }
    }
    object IEnumerator.Current
    {
    get
    {
    if (_position == -1)
    {
    throw new InvalidOperationException();
    }
    return t[_position];
    }
    }
    public void Dispose()
    {
    }
    public bool MoveNext()
    {
    Console.WriteLine("Call Move Next");
    if (_position >= t.Length)
    return false;
    else
    {
    _position++;
    return _position < t.Length;
    }
    }
    public void Reset()
    {
    _position = -1;
    }
    }
    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
    47
    48
    49
    这里实现的是IEnumerator泛型枚举器类,泛型枚举器类的实现中包含一个非泛型的Current属性,返回Object对象的引用,枚举器开始位置的状态为第一个元素之前,_position为-1,因此当我们取到Current之前,应先调用一次MoveNext(),才能得到可枚举项中的第一项,当位置到达可枚举项的最后一项,再次调用MoveNext(),会返回false,取值过程终止,除非位置被重置。
    再来实现IEnumerable枚举类型:

    class Enumeratable1<T> : IEnumerable<T>
    {
    private T[] t;
    public Enumeratable1(T[] a) {
    t = new T[a.Length];
    for (int i = 0; i < t.Length; i++) {
    t[i] = a[i];
    }
    }
    public IEnumerator<T> GetEnumerator()
    {
    return new Enumerator1<T>(t);
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
    return new Enumerator1<T>(t);
    }
    }`
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    这里实现的IEnumerable泛型枚举类,泛型枚举类的实现中包含一个非泛型的GetEnumerator方法,GetEnumerator返回的为之前创建的Enumerator1的泛型构造类的实例,使用泛型数组作为参数传入构造函数,创建Enumerator1的枚举器对象。
    下面使用foreach对Enumerable1的实例进行遍历:

    static void Main() {
    string[] strs = new string[] { "red", "green", "blue" };
    int[] nums = new int[] { 1, 2, 3, 4, 5 };
    Enumeratable1<string> strEnum = new Enumeratable1<string>(strs);
    Enumeratable1<int> numEnum = new Enumeratable1<int>(nums);
    foreach (var temp in strEnum)
    {
    Console.WriteLine(temp);
    }
    Console.ReadKey();
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    输出结果:


    在实现MoveNext()方法时,添加了一行输出,从输出结果可以看出,用foreach做遍历时,先调用了MoveNext()方法,再调用Current属性获得当前项,当取到最后一项时,并没有退出枚举过程,而是再次调用MoveNext()方法,返回值为false后,才退出枚举过程。

    迭代器 和 yield return
    当我们自己实现可枚举类型,在foreach中完成遍历时,需要手动实现IEnumerable枚举接口和IEnumerator枚举器接口。C#2.0以后提供了更为简单的方式去创建可枚举类型和枚举器,那就是迭代器,迭代器会生成枚举类型和枚举器类型。
    举个栗子:

    public IEnumerator<string> Color() //迭代器
    {
    yield return "red";
    yield return "green";
    yield return "blue";
    }
    1
    2
    3
    4
    5
    6
    以上代码就是一个迭代器创建枚举器的过程,如果手动创建枚举器,是需要实现IEnumerator的接口的,而在这里,并没有实现Current,MoveNext(),Reset这些成员,取而代之使用的是yield return语句,简单来说迭代器就是使用一个或多个yield return语句告诉编译器创建枚举器类,yield return语句指定了枚举器中下一个可枚举项,完整的代码:

    class Program{
    static void Main()
    {
    ColorEnumerable color = new ColorEnumerable();
    foreach (var item in color)
    {
    Console.WriteLine(item);
    }
    Console.ReadKey();
    }}
    class ColorEnumerable {
    public IEnumerator<string> GetEnumerator()
    {
    return Color();
    }
    public IEnumerator<string> Color()
    {
    yield return "red";
    yield return "green";
    yield return "blue";
    }}
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    ColorEnumeable类实现了GetEnumerator()方法,为可枚举类型。迭代器可以返回一个枚举器类型,也可以返回一个可枚举类型,例如:

    public IEnumerable<string> Color() {
    yield return "red2";
    yield return "green2";
    yield return "blue2";
    }
    1
    2
    3
    4
    5
    以上代码是利用迭代器返回一个IEnumerable的可枚举类型,完整的代码如下:

    class Program{
    static void Main()
    {
    ColorEnumerable2 color2 = new ColorEnumerable2();
    foreach (var item in color2.Color())
    {
    Console.WriteLine(item);
    }
    Console.ReadKey();
    }}
    class ColorEnumerable2 {
    public IEnumerable<string> Color() {
    yield return "red2";
    yield return "green2";
    yield return "blue2";
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    在foreach的语句中用的是color2.Color(),而不是像上一个例子中直接使用的color,这是因为ColorEnumerable类中已经公开实现了GetEnumerator()方法,但在ColorEnumerable2类中并没有公开实现,所以不能使用foreach直接遍历ColorEnumerable2类的实例,但ColorEnumerable2的方法Color()使用迭代器创建了可枚举类,已经实现了GetEnumerator()方法,所以使用color2.Color()可以得到可枚举类在foreach中进行遍历。我们对ColorEnumerable2作如下修改:

    class ColorEnumerable2 {
    public IEnumerator<string> GetEnumerator() {
    return Color().GetEnumerator();
    }
    public IEnumerable<string> Color() {
    yield return "red2";
    yield return "green2";
    yield return "blue2";
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    便可以在foreach的语句中直接使用ColorEnumerable2的实例进行遍历了。总结一下就是:

    yield reutrn可以根据返回类型告诉编译器创建可枚举类或者是枚举器
    yield return 语句指定了枚举器对象中的下一个可枚举项
    迭代器的执行顺序
    当创建完一个可枚举类型(不管是手动实现或是使用迭代器创建),枚举器实际上可以看做是包含4个状态的状态机(参考C#图解教程):

    Before 首次调用MoveNext()时的状态,初始位置在第一个可枚举项之前
    Running 调用MoveNext后进入该状态。在Running状态下,枚举器检测下一项的位置,当遇到yield return时会进入挂起状态,直到遇到下一个MoveNext(),当遇到yield break时或迭代器结束时,会退出状态
    Suspended 暂时挂起状态,等待下一次MoveNext()调用时唤醒
    After 已经到最后的位置,没有可枚举项
    如下图所示:


    通过代码来具体看这个过程:

    class Program
    {
    static readonly String Padding = new String(' ', 35);
    static IEnumerable<Int32> Enumerable() {
    try {
    Console.WriteLine("{0}进入Enumerable方法", Padding);
    for (int i = 0; i < 4; i++)
    {
    Console.WriteLine("{0}yield return {1} 开始",Padding,i);
    yield return i;
    Console.WriteLine("{0}yield return {1} 结束",Padding,i);
    }
    Console.WriteLine("{0}最后一个yield return 开始", Padding);
    yield return -1;
    Console.WriteLine("{0}最后一个yield return 结束", Padding);
    }
    finally
    {
    Console.WriteLine("{0}Enumerable方法结束",Padding);
    }
    }
    static void Main(string[] args)
    {
    IEnumerable<Int32> ie = Enumerable();
    IEnumerator<Int32> ietor = ie.GetEnumerator();
    Console.WriteLine("开始迭代");
    while (true) {
    Console.WriteLine("调用MoveNext()之前");
    bool result=ietor.MoveNext();
    Console.WriteLine("调用MoveNext()之后,值为:{0}", result);
    if (!result) {
    break;
    }
    Console.WriteLine("Current值为:{0}",ietor.Current);
    }
    Console.ReadKey();
    }
    }
    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
    为了调用枚举器中的MoveNext()方法,使用while循环进行遍历,输出的结果:

    从输出结果可以看出:

    直到调用MoveNext()后才进入迭代器的方法
    遇到yield return,进入暂停挂起状态,,回到开始调用的地方,获取Current,位置被移到下一项
    再次遇到MoveNext()方法,暂停挂起的状态回到运行状态,此时再次遇到yield return,重复暂停挂起的过程
    当进行到最后一项时,迭代器并没有立即结束,而是又执行了一次MoveNext()方法,此时MoveNext()返回的值为false,执行finally中的代码,退出迭代器
    迭代器的退出除了当迭代器状态结束时会发生,使用yield break也会使迭代器退出:

    try {
    Console.WriteLine("{0}进入Enumerable方法", Padding);
    for (int i = 0; i < 4; i++)
    {
    Console.WriteLine("{0}yield return {1} 开始",Padding,i);
    yield return i;
    Console.WriteLine("{0}yield return {1} 结束",Padding,i);
    }
    yield break;
    Console.WriteLine("{0}最后一个yield return 开始", Padding);
    yield return -1;
    Console.WriteLine("{0}最后一个yield return 结束", Padding);
    }
    finally
    {
    Console.WriteLine("{0}Enumerable方法结束", Padding);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    输出结果:

    在迭代器的foreach循环结束后使用yield break语句,yield break 语句后的迭代过程并没有进行,而是把MoveNext()的返回值设置为false,调用MoveNext()方法后进入到finally中,结束迭代过程。关于迭代器的退出:

    在正常迭代进行到最后一项时,迭代没有结束MoveNext()的值被设置为false,再次调用MoveNext(),结束迭代过程
    遇到yield break语句,MoveNext()的值被设置为false,再次调用MoveNext(),结束迭代过程
    yield return语句只是让迭代器状态暂停挂起,等待下一次的MoveNext()调用,继续进行yield之后的语句
    Unity中的StartCoroutine与yield return
    使用过Unity的延时WaitForSeconds()的朋友们一定不会对StartCoroutine和yield return这两个关键字感到陌生。UnityGems.com给出了协程的定义:
    A coroutine is a function that is executed partially and, presuming suitable conditions are met, will be resumed at some point in the future until its work is done.
    即协程是一个分部执行,遇到条件(yield return 语句)会挂起,直到条件满足才会被唤醒继续执行后面的代码。
    先来个例子:

    bool isStartCall = false;
    bool isUpdateCall = false;
    bool isLateUpdateCall = false;
    void Start () {
    if (!isStartCall) {
    Debug.Log("Start Call Begin");
    StartCoroutine("StartCoroutines");
    Debug.Log("Start Call End");
    isStartCall = true;
    }
    }
    IEnumerator StartCoroutines() {
    Debug.Log("StartCoroutine Call Begin");
    yield return null;
    Debug.Log("StartCoroutine Call End");
    }
    void Update() {
    if (!isUpdateCall) {
    Debug.Log("Update Call Begin");
    StartCoroutine("UpdateCoroutines");
    Debug.Log("Update Call End");
    isUpdateCall = true;
    }
    }
    IEnumerator UpdateCoroutines()
    {
    Debug.Log("UpdateCoroutine Call Begin");
    yield return null;
    Debug.Log("UpdateCoroutine Call End");
    }
    void LateUpdate() {
    if (!isLateUpdateCall) {
    Debug.Log("LateUpdate Call Begin");
    StartCoroutine("LateUpdateCoroutines");
    Debug.Log("LateUpdate Call End");
    isLateUpdateCall = true;
    }
    }
    IEnumerator LateUpdateCoroutines()
    {
    Debug.Log("LateUpdateCoroutine Call Begin");
    yield return null;
    Debug.Log("LateUpdateCoroutine Call End");
    }
    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
    在Start(),Update()和LateUpdate()中分别使用StartCoroutine()方法,传入的参数是一个实现了枚举器的方法名的字符串,枚举器的实现使用的迭代器,通过yield return语句完成,下面是输出结果:

    输出结果中包含较多的信息,先来看看Start()方法中的StartCoroutine的执行顺序:

    输出Start Call Begin
    进入StartCoroutines()方法,输出StartCoroutine Call Begin
    遇到yield return语句,暂时挂起,回到调用的地方
    输出Start Call End
    Start()方法执行完毕
    当Start()方法执行完毕的时候,程序并没有回到StartCoroutines()内,而是按照上面的过程走完Update(),LataUpdate()方法之后才回到StartCoroutines()内,之后是UpdateCoroutines()和LateUpdateCoroutines(),这是为什么呢?这里涉及到Coroutine的执行顺序,Unity中,Start(),Update(),LateUpdate()依次执行,这没什么好说的,Coroutine的执行是在每一帧的LateUpdate()方法之后,这里需要注意一下,在每个Coroutine的yield return语句中传入的是null,程序会停留一帧,因此是在直到LateUpdate()方法执行完之后才会进入到StartCoroutine()的方法内。
    之前我们看到的迭代器退出是在迭代器执行到MoveNext()方法时返回的值为false的情况下才会退出,但是刚才的示例代码中并没有看到MoveNext()的取用,原因在于这个调用和检测过程由Unity完成了:

    Unity在每帧调用 协程(迭代器)MoveNext() 方法,如果返回 true ,就从当前位置继续往下执行。
    在每个Coroutines里只有一个可列举项,Unity在LateUpdate()执行完后开始调用每个Coroutines中的MoveNext(),(执行一次后MoveNext()的返回值已经为fasle),Coroutines结束暂停挂起,回到运行状态,输出Debug.Log()后,下一帧再次调用MoveNext(),由于已经是false值,Coroutines退出迭代。
    Unity中在创建IEnumerator枚举器时,yield return 后面可以有如下表达式:

    null - the coroutine executes the next time that it is eligible
    WaitForEndOfFrame - the coroutine executes on the frame, after all of the rendering and GUI is complete
    WaitForFixedUpdate - causes this coroutine to execute at the next physics step, after all physics is calculated
    WaitForSeconds - causes the coroutine not to execute for a given game time period
    WWW - waits for a web request to complete (resumes as if WaitForSeconds or null)
    Another coroutine - in which case the new coroutine will run to completion before the yielder is resumed
    其中有关延时的处理在StartCoroutine()中被特殊处理了,Unity在每一帧调用这几个类的MoveNext之前会先判断延时的条件是否已经满足,如果满足,才会执行后面的MoveNext()操作,如果不满足,会跳出迭代器的方法下一帧再次进行检测,直到满足延时条件,执行MoveNext()操作,当MoveNext()的值为false,退出迭代器。关于被特殊处理的过程,可以参考Coroutine,你究竟干了什么这篇博客。

    Coroutine的停止
    Unity中Coroutine停止掉的方法,通过将附有脚本的gameObject.SetActive(false)可以停止掉Corotine,就像C#中在迭代器使用yield break一样,不过再重新设置为gameObject.SetActive(true)时,Coroutine并不会恢复。例如,将Update()中的代码修改为:

    void Update() {
    if (!isUpdateCall) {
    Debug.Log("Update Call Begin");
    StartCoroutine("UpdateCoroutines");
    Debug.Log("Update Call End");
    isUpdateCall = true;
    gameObject.SetActive(false);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    添加了一行gameObject.SetActive(false),再来看一下输出结果:

    当代码执行到UpdateCoroutines()时,输出”UpdateCoroutine Call Begin”,遇到yield return迭代器进入暂停挂起状态,回到Update()中,输出”Update Call End”,遇到gameObject.SetActive(false),此时开启的Coroutines全部终止,后面的正常方法LateUpdate()和三个Coroutine里面的输出无法进行。
    这是gameObject.SetActive(false)放在Coroutine的外部,如果放在内部会是什么情况?修改之前的代码,将gameObject.SetActive(false)放到UpdateCoroutines里面:

    IEnumerator UpdateCoroutines()
    {
    Debug.Log("UpdateCoroutine Call Begin");
    yield return null;
    gameObject.SetActive(false);
    Debug.Log("UpdateCoroutine Call End");
    }
    1
    2
    3
    4
    5
    6
    7
    再来看看输出结果:

    输出结果是11行,只有最后一个LateUpdateCoroutines中yield return后面的LateUpdateCoroutine Call End没有并执行,通过之前的分析过程,这里的输出结果并不难解释,当代码再次回到UpdateCorotineds的yield return语句之后,前面的输出应该是“StartCoroutines Call End”,这里需要注意的是,尽管 gameObject.SetActive(false)在Debug.Log(“UpdateCoroutine Call End”)语句之前,但是”UpdateCoroutine Call End”还是会输出,也就是说,当gameObject.SetActive(false)在迭代器内部时,代码并不是一走到 gameObject.SetActive(false);就立即终止,而是会离开迭代器之前将离开前的代码执行完。
    ————————————————
    版权声明:本文为CSDN博主「O213」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/u013477973/article/details/65635737

  • 相关阅读:
    Java 集合系列06之 Vector详细介绍(源码解析)和使用示例
    Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例
    Java 集合系列02之 Collection架构
    Java 集合系列01之 总体框架
    [转载] 散列表(Hash Table) 从理论到实用(下)
    [转载] 散列表(Hash Table)从理论到实用(中)
    [转载] 散列表(Hash Table)从理论到实用(上)
    Android 之窗口小部件高级篇--App Widget 之 RemoteViews
    Android控件之GridView
    红黑树(一)之 原理和算法详细介绍
  • 原文地址:https://www.cnblogs.com/liuslayer/p/11422765.html
Copyright © 2011-2022 走看看