如果我多次调用
Run()
,我希望看到消息“一”、“二”和“三”,但实际情况并非如此,我可以看到第一个,然后 MoveNext()
返回 false。我在这里错过了什么?
public class Test
{
private IEnumerator<Object> routine;
public void Setup()
{
routine = CountOne();
}
public void Run()
{
routine.MoveNext();
}
private IEnumerator<Object> CountOne()
{
Console.WriteLine("One");
yield return CountTwo();
}
private IEnumerator<Object> CountTwo()
{
Console.WriteLine("Two");
yield return CountThree();
}
private IEnumerator<Object> CountThree()
{
Console.WriteLine("Three");
yield return null;
}
}
避免使用
Object
:可能有助于更好地理解事物
public class Test
{
private IEnumerator<int> routine;
public void Setup()
{
routine = CountOne();
}
public void Run()
{
routine.MoveNext();
}
private IEnumerator<int> CountOne()
{
Console.WriteLine("One");
yield return CountTwo(); // Cannot implicitly convert type ...
}
private IEnumerator<int> CountTwo()
{
Console.WriteLine("Two");
yield return CountThree(); // Cannot implicitly convert type ...
}
private IEnumerator<int> CountThree()
{
Console.WriteLine("Three");
yield return 0;
}
}
当您尝试
yield return CountTwo();
或 yield return CountThree();
时,这会出现一些编译时错误。那是因为这些函数返回 IEnumerable<int>
s 并且您只能从返回 yield return
的方法中执行 int
和 IEnumerator<int>
。
您的原始代码中没有发生该错误,因为从技术上讲,
IEnumerator<Object>
是一个 Object
并且可以转换为一个,即使这不是您 yield return
ed 时的意图。
要获得您正在寻找的行为,请将这些从
yield return
更改为仅 return
,或者创建一个 foreach
循环到 yield return
从这些方法返回的 IEnumerator 中的每个项目。
这似乎来自 Unity,我有一段时间没有使用它,但请尝试枚举枚举器:
public class Test
{
private IEnumerator routine;
public void Run()
{
routine ??= CountOne();
while (routine.MoveNext())
{
// no-op
}
}
private IEnumerator CountOne()
{
Debug.Log($"One");
var countTwo = CountTwo();
while (countTwo.MoveNext())
{
yield return countTwo.Current;
}
}
private IEnumerator CountTwo()
{
Debug.Log($"Two");
var countThree = CountThree();
while (countThree.MoveNext())
{
yield return countThree.Current;
}
}
private IEnumerator CountThree()
{
Debug.Log($"Three");
yield return null;
}
}
否则,在
CountOne
中,您只需返回一个作为下一个枚举器的项目,而无需实际处理它(即 CountOne
的返回类型有效IEnumerator<IEnumerator>
)。
yield return
支持交替返回IEnumerator
和IEnumerable
。来自文档:
迭代器的调用不会立即执行它...
...当您开始迭代迭代器的结果时,将执行迭代器直到到达第一个 yield return 语句。然后,暂停迭代器的执行,调用者获取第一个迭代值并对其进行处理。在每个后续迭代中,迭代器的执行在导致先前暂停的 yield return 语句之后恢复,并继续直到到达下一个 yield return 语句。当控制到达迭代器或 yield break 语句的末尾时,迭代完成。
这里发生的事情是
yield
关键字与方法声明的返回类型混淆。
当我们将
yield
放入声明返回 IEnumerator<Object>
的方法中时,它期望 yield
表达式的结果为 Object
。如果我们将 yield
放在声明返回 IEnumerator<string>
的方法中,(如“一”、“二”、“三”),它期望 yield
表达式的结果为 string
(不是IEnumerator<string>
!
但这不是你所拥有的。 相反,嵌套的
CountTwo()
和 CountThree()
方法也返回 IEnumerator
类型,原始代码编译的唯一原因是这些类型可分配给 Object
。
同样重要的是,这是三个不同的枚举器,而不是同一枚举器中的三个收益率。当代码第一次调用
Run()
时,只涉及 CountOne()
的枚举器,此时它已完成,因为它只有一个元素。
如果我多次调用 Run(),我希望看到消息“一”、“二”和“三”
让我们实现这一目标!这里有两个例子:
public class Test
{
private IEnumerator routine;
public void Setup()
{
routine = (new string[] {"One", "Two", "Three"}).GetEnumerator();
}
public void Run()
{
routine.MoveNext();
Console.WriteLine(routine.Current);
}
}
// Designed to look more like the original
public class Test2
{
private IEnumerator routine;
public void Setup()
{
routine = CountOne();
}
public void Run()
{
routine.MoveNext();
// queue up the next enumerator
routine = (IEnumerator)routine.Current;
}
public IEnumerator<IEnumerator<IEnumerator<string>>> CountOne()
{
Console.WriteLine("One");
yield return CountTwo();
}
public IEnumerator<IEnumerator<string>> CountTwo()
{
Console.WriteLine("Two");
yield return CountThree();
}
public IEnumerator<string> CountThree()
{
Console.WriteLine("Three");
yield return null;
}
}
看到他们在这里工作: