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

  • 相关阅读:
    解决ListView异步加载数据之后不能点击的问题
    android点击实现图片放大缩小 java技术博客
    关于 数据文件自增长 的一点理解
    RAC 实例不能启动 ORA1589 signalled during ALTER DATABASE OPEN
    Linux 超级用户的权利
    RAC 实例 迁移到 单实例 使用导出导入
    Shell 基本语法
    Linux 开机引导与关机过程
    RAC 实例不能启动 ORA1589 signalled during ALTER DATABASE OPEN
    Oracle RAC + Data Guard 环境搭建
  • 原文地址:https://www.cnblogs.com/liuslayer/p/11422765.html
Copyright © 2011-2022 走看看