记录一下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");
}
通过上面的测试,完全可以自己实现一个协程,那么实际协程的实现方式也应该于此类似。特此记录,方便理解。