【问题标题】:Breaking apart code using yield return into different methods使用 yield return 将代码分解为不同的方法
【发布时间】:2013-06-03 22:16:23
【问题描述】:

我正在实现一个用于运行游戏 AI 脚本的光纤系统,但遇到了一个小问题。

我在脚本中使用 yield return x 来表示等待 x 帧。我想封装计算 x 帧的逻辑,因为其他事件可以修改要等待的帧数。我理想的解决方案如下:

public Boolean removeScript = false;

public IEnumerable update()
{
    while(true)
    {
        shootAtPlayer()
        wait(30)
    }
}

private IEnumerable wait(int x)
{
    yield return removeScript ? -1 : x;
}

wait() 要么指示产生返回 30,要么在不再需要脚本时返回 -1。迭代器将通过删除脚本来处理 -1 返回。如果 x > 0,迭代器将每帧递减返回值直到 0,然后再次调用 update()。

但是这不起作用,因为从 wait() 返回的 yield 当然不会传播到 update 方法。这会导致代码重复、模块性变差和代码可读性降低:

public IEnumerable update()
{
    while(true)
    {
        shootAtPlayer()
        yield return removeScript ? -1 : x;
    }
}

我想知道是否有更好的方法来构建它?还是我缺少在这种情况下有用的语言功能?

【问题讨论】:

  • 在这里使用yield return 到底有什么意义?谁打电话给update,为什么它不能只是一个普通的旧函数?
  • 调用者有一个更新循环(每帧调用一次),如果当前枚举数为 0,则在上述脚本上调用更新。如果当前枚举数 > 0,则每帧递减一次枚举数,直到 0 . 以这种方式实现的原因是允许脚本在执行此操作然后等待 60 帧然后执行此方式而不是在第 0 帧执行此操作然后在第 60 帧执行此操作。它使脚本更多易于跟踪,因为它们操作类似于单独的子程序。最好使用 yield return 而不是为每个脚本生成一个线程并使用 sleep 等
  • 您正在尝试使用 yield 创建协程。不幸的是,yield 是一个略微受限的协同例程版本。正如您所发现的,您只能在 IEnumerable 函数的主体内直接使用 yield,而不是嵌套到另一个方法中。如果您考虑一下编译器在幕后做了什么魔法,这个限制是有道理的。

标签: c# yield-return


【解决方案1】:

在这种特定情况下,解决方案比较简单:让Wait() 只返回整数:

public IEnumerable Update()
{
    while (true)
    {
        ShootAtPlayer();
        yield return Wait(30);
    }
}

private int Wait(int x)
{
    return removeScript ? -1 : x;
}

在更复杂的情况下,您可以使用foreach,但这会使语法更加冗长:

public IEnumerable Update()
{
    while (true)
    {
        ShootAtPlayer();

        foreach (var x in Wait(30))
            yield return x;
    }
}

private IEnumerable Wait(int x)
{
    yield return removeScript ? -1 : x;
}

如您所见,您可以(ab)使用yield return 来实现光纤,但yield return 从来没有为此而设计,所以它不会那么好用。

为这种异步延续所做的是新的async-await。这样,您的代码可能看起来像这样:

public async Task Update()
{
    while (true)
    {
        ShootAtPlayer();
        await Wait(30);
    }
}

private async Task Wait(int x)
{
    await fiber.Wait(removeScript ? -1 : x);
}

最后一点,我认为您使用removeScript 的方式不是一个好主意。脚本的结束应该由实际完成的Update() 方法(可枚举没有更多的项目,或者Task 完成)来表示,而不是通过返回一个神奇的值。

【讨论】:

    猜你喜欢
    • 2012-03-26
    • 1970-01-01
    • 2011-02-08
    • 1970-01-01
    • 2021-07-10
    • 1970-01-01
    • 2019-11-28
    • 2016-05-28
    • 1970-01-01
    相关资源
    最近更新 更多