繁体   English   中英

C# 中的 IEnumerator 是什么,它在 Unity 中有什么用?

[英]What Is an IEnumerator In C# and what is it used for in Unity?

最近看到Unity中使用C#生成对象的教程。 因为老师像这样使用了 function

   public IEnumerator CallSpawner()
   {
      yield return new WaitForSeconds(0.5f);
      SpawnObstacles();
   }

我想问一下IEnumerator function有什么用,难道不能通过这个流程

float diffTime = 0f;
private void Update()
{
    if(Time.time - diffTime == 0.5f)
    {
        diffTime = Time.time;
        SpawnObstacles();
    }
}

我阅读了文档但无法理解..

IEnumerator function有什么用

IEnumerator没有 function,它是一个返回类型 C# 也没有函数(但我知道你的意思)——在 C# 中我们称它们为方法

I Enumerator 被称为意味着它是一个接口,所以任何实现 IEnumerator 接口的 class 都可以通过该方法返回

实际上,在这种使用中,它实际上更像是一种 hack,而不是打算提供枚举器的真实意图,即逐步步枪通过(或生成)事物的集合。

当您在方法中使用yield return语句时,“发生了一些魔法”,这不是经典意义上的return ,而是创建了一个工具,代码可以从它停止的地方恢复(调用返回的枚举器中的下一个项目将导致代码从 yield 之后恢复,而不是重新开始)。

如果您查看 MSDN 的 yield 示例:

public class PowersOf2
{
    static void Main()
    {
        // Display powers of 2 up to the exponent of 8:
        foreach (int i in Power(2, 8))
        {
            Console.Write("{0} ", i);
        }
    }

    public static System.Collections.Generic.IEnumerable<int> Power(int number, int exponent)
    {
        int result = 1;

        for (int i = 0; i < exponent; i++)
        {
            result = result * number;
            yield return result;
        }
    }

    // Output: 2 4 8 16 32 64 128 256
}

循环由i控制; 如果这不是yield return那么这将不是预期的 function (它不能返回一个开始的枚举器,但我们将忽略它)。 假设它只是一个正常的return ,循环根本不会循环; 代码会进入,开始循环,点击return ,然后只返回一个数字一次,循环所在的所有 memory 都会被遗忘。

通过使它成为yield return ,返回一个枚举器,并设置一小组“保存状态”,循环可以记住i的当前值 - 每次您要求下一个值时,代码从它所在的位置恢复离开(即在 yield 之后),循环再次循环并产生不同的值。 这当然会一直持续到最大值。此时返回的枚举器说它没有更多的项目

您也可以永远屈服。如果代码永远无法逃脱循环,那么它将永远屈服/生成


在这种情况下,您必须使用yield return new WaitForSeconds因为这就是 WaitForSeconds 的工作方式。 Yielding 将一个枚举器放弃给调用方法,然后调用方法可以自由地枚举它。 从文档看来,这是故意在下一帧完成的,因此使用 yield(可能重复)是一种安排跨多个帧出现的代码块的方法,而无需某种外部 state 管理来记住进程在哪里最多和罗嗦

  • 如果 state = 1 则关上门并将 state 加 1,
  • else if state = 2 then light the torch and add 1
  • 否则如果 state = 3...”。

你可以

  • 屈服,
  • 关门,
  • 屈服,
  • 点燃火把,
  • 屈服..

我们不能通过这个过程来做吗

当然,看起来很合理; 每秒看时钟 100 次,如果自从您第一次看时钟后已经过去 0.5 秒,则生成障碍物

我想(从未使用过 Unity;除了阅读这个函数的文档之外,不要自称对它一无所知)您的更新循环还有很多事情要做,所以将一个过程交给专心等待然后做比花所有时间看时钟并进行可能复杂的计算以计算是否应该做某事更有效; 生活中大多数以每 x 毫秒轮询开始的事情都受益于切换到“如果事件发生,对其做出反应”的工作方式

您正在查看的代码仅存在于 Unity3d 和 DotNet Framework < 4.0 中。

您找不到任何文档的原因是:

  1. 这是一个邪恶的 hack(由 Jeffrey Richter 首创)滥用 C# 编译器的 IEnumerable 状态机生成器功能。 有关示例,请参见https://www.codeproject.com/Articles/39124/Learn-How-to-Simplify-the-Asynchronous-Programming
  2. 微软人员早就将此 hack 添加到 dot.net 编译器中,他们将此功能称为async / await

不幸的是,Unity3d 是基于旧版本的 Mono,它不支持async / await

幸运的是, async / await的大部分功能都可以使用这种方法来完成

go 使用 Jeff 的 Power Threading Library 方法有几个原因。

  1. 流动。 您只有一个方法来设置等待、定义等待时间并执行有效负载,并按此顺序进行。
  2. 表现。 您建议的代码每秒将运行多次,从而在轮询超时完成时减慢系统速度。 相反,PTL 方法提供了一个在未来安排的回调事件。
  3. 清理。 您的代码将继续运行,在SpawnObstacles()运行一次后检查 0.5 长。
  4. 时间模糊。 如果您的 PC 由于滞后、性能等原因跳帧。您可能已经完全跳过了 0.5 帧。 此外,0.5d 可能永远不会发生(请参阅 Jon Skeet 的 Pony Fail 帖子,您正在进行双重比较)。 计时器/回调肯定会在 0.5 秒后的某个时间触发。

但主要原因必须是 Flow。 在这个非常简单的例子中,我们不会轻易混淆。 但是想象一下下面的效果。

  • 每 10 秒会产生新的敌人
  • 每次产卵都将遵循斐波那契数列
  • 在 10 秒时将生成 1 个敌人
  • 20岁时 1个敌人
  • 30岁时 2个敌人
  • 40岁时 3个敌人
  • 50岁时 5个敌人
  • 60年代8敌
  • ETC

这将很难用update方法编写。

但是,您可以使用 IEnumerable 轻松完成。

public IEnumerator CallSpawner()
{
  int current = 1;
  int last = 0;
  while(true)
  {
      yield return new WaitForSeconds(10f);
      SpawnEnemies(current);
      var next = current + last;
      last = current;
      current = next;
   }
}

作为奖励,您会注意到所有变量都很好地封装在方法中,而不是在自由浮动字段中。

这意味着您可以运行多个 CallSpawner() 而不会受到它们的干扰。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM