记录一下C# 枚举器IEnumerator以及协程StartCoroutine的理解

记录一下C# 枚举器IEnumerator以及协程StartCoroutine的理解:

IEnumerator 与yield return:

yield return 可以理解为语法糖,它简化了创建枚举器的整个过程。下面代码是yield return 生成的枚举器。

    public IEnumerator Test()
    {
        Debug.LogError("[" + Time.frameCount + "] a");
        yield return 1;
        Debug.LogError("[" + Time.frameCount + "] b");
        yield return 2;
        Debug.LogError("[" + Time.frameCount + "] c");
        yield return 3;
        var temp = GetRandom();
        if (temp > 5)
        {
            Debug.LogError("[" + Time.frameCount + "] d");
            yield return 4;
        }

        Debug.LogError("[" + Time.frameCount + "] e");
        yield return 5;
    }

接下来自己实现一个枚举器:

//不使用Yield Return 实现的枚举器。功能与函数Test()相同。
public class MyEnumerator : IEnumerator
{
    public int index = 0;
    public int value;

    public bool MoveNext()
    {
        if (index == 0)
        {
            Debug.LogError("[" + Time.frameCount + "] a");
            value = 1;
            index += 1;
            return true;
        }
        else if (index == 1)
        {
            Debug.LogError("[" + Time.frameCount + "] b");
            value = 2;
            index += 1;
            return true;
        }
        else if (index == 2)
        {
            Debug.LogError("[" + Time.frameCount + "] c");
            value = 3;
            index += 1;
            return true;
        }
        else if (index == 3)
        {
            Debug.LogError("GetRandom");
            var temp = Random.Range(0, 10);
            if (temp > 5)
            {
                Debug.LogError("[" + Time.frameCount + "] d");
                value = 4;
                index += 1;
                return true;
            }
            else
            {
                Debug.LogError("[" + Time.frameCount + "] e");
                value = 5;
                index = 99;
                return true;
            }
        }
        else if (index == 4)
        {
            Debug.LogError("[" + Time.frameCount + "] e");
            value = 5;
            index = 99;
            return true;
        }
        else if (index == 99)
        {
            return false;
        }

        return false;
    }

经测试两者功能相同。

证明前面想法:yield return 可以理解为语法糖,它简化了创建枚举器的整个过程。而yield return 返回的就是value,两个yield return 之间的代码作为MoveNext的某一种情况的处理。

foreach 也是一个语法糖,功能与如下代码功能相同。

    private void ShowMethod01(Test test2)
    {
        Debug.LogError(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ShowMethod01");
        foreach (var item in test2)
        {
            Debug.LogError(">>>> "+item);
        }
    }

    private void ShowMethod02(Test test)
    {
        Debug.LogError(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ShowMethod02");
        var temp = test.GetEnumerator();
        while (true)
        {
            var temp2 = temp.MoveNext();
            if (!temp2)
                break;
            Debug.LogError(">>>> "+temp.Current);
        }
    }

StartCoroutine:

协程应该是在"Update"中调用枚举的MoveNext方法。类似于下面代码。

        //基础协程模拟;
        if (test != null)
        {
            Debug.LogError("Start a MoveNext");
            var bok = test.MoveNext();
            if (!bok)
                test = null;
        }

那么如何处理Yield return new WaitForSecond的功能呢?应该是在"Update"中有类似如下处理

    public void MyCoroutineFunc(ref IEnumerator test2)
    {
        if (test2 != null)
        {
            bool isNoNeedWait = true, isMoveOver = true;
            var va = test2.Current;
            if (va is MyWaitForSecond wait)
            {
                wait.Update(Time.deltaTime);
                isNoNeedWait = !wait.keepWaiting;
            }
            else if (va is MyWaitUntil wait2)
            {
                isNoNeedWait = !wait2.keepWaiting;
            }

            if (isNoNeedWait) 
            {
                isMoveOver = test2.MoveNext();
            }
            
            if (!isMoveOver) //结束。
            {
                test2 = null;
            }
        }
    }

先判断IEnumerator.Current的类型,针对不同的类型进行处理。当处理结束后,再MoveNext;

自定义一个WaitForSecond,当yield return new MyWaitforSecond的时候,IEnumerator.Current == MyWaitforSecond,先等待MyWaitforSecond结束再继续,见上面代码。

public class MyWaitForSecond : CustomYieldInstruction
{
    protected float totalTime;
    protected float useTime;

    public MyWaitForSecond(float time)
    {
        totalTime = time;
        useTime = 0;
    }

    public void Update(float deltaTime)
    {
        useTime += deltaTime;
    }

    public override bool keepWaiting => useTime < totalTime;
}

那么自己实现一个协程:

//yield return StartCoroutine  yield return IEnumerator 功能相同,但是推荐使用前者。
//https://blog.csdn.net/wanfping123/article/details/105677604
//这里做处理的办法是将IEnumerator转化成MyCoroutine.
public class MyCoroutine : CustomYieldInstruction
{
    public IEnumerator test;
    public static MyCoroutine test2;
    public MyCoroutine(IEnumerator test)
    {
        this.test = test;
    }

    public void Update()
    {
        if (test != null)
        {
            bool isNoNeedWait = true, isMoveOver = true;
            var va = test.Current;
            if (va is MyWaitForSecond wait) //yield return new MyWaitForSecond()
            {
                wait.Update(Time.deltaTime);
                isNoNeedWait = !wait.keepWaiting;
            }
            else if (va is MyWaitUntil wait2)//yield return new MyWaitUntil()
            {
                isNoNeedWait = !wait2.keepWaiting;
            }
            else if (va is MyCoroutine wait3)//yield return StartMyCoroutine()
            {
                wait3.Update();
                isNoNeedWait = !wait3.keepWaiting;
                
            }else if (va is IEnumerator wait4) //yield return IEnumerator  
            {
                if(test2==null)
                    test2= new MyCoroutine(wait4);
                if(test2!=null)
                    test2.Update();
                if(test2!=null)
                    isNoNeedWait = !test2.keepWaiting;
                if (isNoNeedWait)
                    test2 = null;
            }
            if (isNoNeedWait)
            {
                isMoveOver = test.MoveNext();
            }

            if (!isMoveOver)
            {
                test = null;
            }
        }
    }

    public override bool keepWaiting => test != null;
}

开启协程:

    public MyCoroutine StartMyCoroutine(IEnumerator test)
    {
        return new MyCoroutine(test);
    }

在"Update"中处理协程:

    void Update()
    {
        //基础协程模拟;
        if (test != null)
        {
            Debug.LogError("Start a MoveNext");
            var bok = test.MoveNext();
            if (!bok)
                test = null;
        }

        //带wait协程模拟。
        MyCoroutineFunc(ref test2);

        //协程调用协程模拟。
        if (test3 != null)
        {
            test3.Update();
            if (!test3.MoveNext())
                test3 = null;
        }
    }

Yield return StartCoroutine 与 Yield return IEnumerator :

协程中Yield return StartCoroutine 与 Yield return IEnumerator 功能相同,在早期Unity3d版本中,Yield return IEnumerator只能用在JS中,C#必须用  Yield return StartCoroutine,后来就没有这个要求了,但是仍然建议使用Yield return StartCoroutine。

对于Yield return IEnumerator 的处理,在MyCoroutine中我把它转为MyCoroutine对象来处理了。

接下来结果对比:

    public IEnumerator Test3()
    {
        Debug.LogError("Test3 [" + Time.frameCount + "] a");
        yield return 99;
        Debug.LogError("新协程 [" + Time.frameCount + "] b");
        yield return StartMyCoroutine(Test4());
        Debug.LogError("Test3 [" + Time.frameCount + "] c");
        yield return 9999;
        Debug.LogError("Test3 等待10帧[" + Time.frameCount + "] d");
        var currrentFrameCount = Time.frameCount;
        bool Temp() => Time.frameCount - currrentFrameCount >= 10;
        yield return new MyWaitUntil(Temp);
        Debug.LogError("Test3 [" + Time.frameCount + "] e");
        yield return Test5();
    }

    public IEnumerator Test31()
    {
        Debug.LogError(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        Debug.LogError("Test3 [" + Time.frameCount + "] a");
        yield return 99;
        Debug.LogError("新协程 [" + Time.frameCount + "] b");
        yield return StartCoroutine(Test4());
        Debug.LogError("Test3 [" + Time.frameCount + "] c");
        yield return 9999;
        Debug.LogError("Test3 等待10帧[" + Time.frameCount + "] d");
        var currrentFrameCount = Time.frameCount;
        bool Temp() => Time.frameCount - currrentFrameCount >= 10;
        yield return new WaitUntil(Temp);
        Debug.LogError("Test3 [" + Time.frameCount + "] e");
        yield return Test51();
        
    }

    public IEnumerator Test4()
    {
        Debug.LogError("Test4 [" + Time.frameCount + "] 4a");
        yield return 11;
        Debug.LogError("Test4 [" + Time.frameCount + "] 4b");
        yield return 111;
    }

    public IEnumerator Test5()
    {
        Debug.LogError("Test5 [" + Time.frameCount + "] 5a");
        yield return 11;
        Debug.LogError("Test5 [" + Time.frameCount + "] 5b");
        yield return 111;
        Debug.LogError("Test5 [" + Time.timeSinceLevelLoad + "] 5c");
        yield return  new MyWaitForSecond(1.0f);
        Debug.LogError("Test5 [" + Time.timeSinceLevelLoad + "] 5c");
    }
    public IEnumerator Test51()
    {
        Debug.LogError("Test51 [" + Time.frameCount + "] 5a");
        yield return 11;
        Debug.LogError("Test51 [" + Time.frameCount + "] 5b");
        yield return 111;
        Debug.LogError("Test51 [" + Time.timeSinceLevelLoad + "] 5c");
        yield return  new WaitForSeconds(1.0f);
        Debug.LogError("Test51 [" + Time.timeSinceLevelLoad + "] 5c");
        
    }

通过上面的测试,完全可以自己实现一个协程,那么实际协程的实现方式也应该于此类似。特此记录,方便理解。

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值