【问题标题】:Can't remove item from list in XNA无法从 XNA 的列表中删除项目
【发布时间】:2014-04-02 03:29:06
【问题描述】:

我正在做一个 Flappy Bird 克隆作为练习,因为我最近开始在 XNA 上编程,但我遇到了这个我无法理解的错误。

我在 Update() 函数中包含了以下代码,它的工作是在管道离开屏幕时删除管道,以免在创建更多管道时无限向左移动:

//Pipe despawner
foreach (var pipe in pipes)
{
   if (pipe.position1.X <= -180)
   {
         pipes.Remove(pipe);
   }
}

游戏运行良好,直到第一个管道离开屏幕并且调试器暂停并通过以下消息向这部分代码发出信号:

An unhandled exception of type 'System.InvalidOperationException' occurred in mscorlib.dll

Additional information: Colección modificada; puede que no se ejecute la operación de enumeración.

很抱歉,第二部分是西班牙语,这是我系统的语言,但希望你知道如何解决这个问题。

考虑到游戏的简单性以及无限生成管道对性能的小影响,我相信我不能简单地包含这部分代码,但我不想尽快采用这种做法我开始学习游戏编程。

【问题讨论】:

标签: c# xna


【解决方案1】:

问题是您在枚举集合时正在修改集合(通过删除管道)。这将总是在 .NET 中引发异常(如果您曾经在多线程环境中执行此操作,请注意这一点,因为您可能会因此得到一些令人讨厌的不可重现的异常) .

解决它的一个简单方法是在枚举时保留一个“kill”列表:

List killList = new List();

foreach (var pipe in pipes)
{
   if (pipe.position1.X <= -180)
   {
      killList.Add(pipe)
   }
}

更简单:

IEnumerable<Pipe> killList = pipes.Where (p => p.position1.X < -100);

无论哪种方式,枚举这个 new 集合,并从主集合中删除匹配元素(从而避免错误情况):

foreach (Pipe p in killList)
   pipes.Remove(p);

你就完成了!再次,注意线程。如果您在另一个线程中创建新管道,您可能很容易与这个“冲突”并导致异常。如果是这种情况,请确保在这些代码段周围加锁。

注意,如果您使用 LINQ 方法,您实际上可以内联“killList”:

foreach (Pipe p in pipes.Where(p => p.Position1.X <= -100))
   pipes.Remove(p);

正如@rot13 建议的那样,您也可以只运行 RemoveAll,将“Where”语句中的谓词传递给它,例如:

pipes.RemoveAll(p => p.Position1.X <= -100);

就这么简单:)

【讨论】:

  • 非常感谢先生,完美的解释和完美的解决方案!
  • 您可以使用 RemoveAll 扩展方法从示例中摆脱循环,如下所示:pipes.RemoveAll(item =&gt; item.position1 &lt;= -100);。您也可以反向枚举元素,然后删除其中一个不会引发异常。
  • @rot13 谢谢!我在回答中包含了您的建议。
【解决方案2】:

您无法从当前使用foreach 迭代的集合中删除项目。

要么使用for 循环并自己调整索引,要么使用第二个集合来处理要删除的管道:

List<Pipe> piesToRemove = new List<Pipe>();
// First you flag every pipes you need to remove
foreach (var pipe in pipes)
{
   if (pipe.position1.X <= -180)
   {
         pipesToRemove.Add(pipe);
   }
}

//Now you remove them from your original collection
foreach (var pipe in pipesToRemove)
{
    pipes.Remove(pipe);
}

【讨论】:

    【解决方案3】:

    您可能只想将它们移动到屏幕的另一侧并向上/向下移动,而不是移除管道。

    【讨论】:

    • 是的,我也是这么想的。他可以列出一个小清单,然后将那些不在屏幕上的清单移出。
    • 这绝对是一种性能更高的方法,并且是 OP 考虑的一个好主意,尤其是随着每个时间单位“回收”对象的数量增加。
    【解决方案4】:

    这是说您在循环时不能修改管道。试试这样的:

            //Pipe despawner
            var unusedPipes = new List<Pipe>();
    
            foreach (var pipe in pipes)
            {
                if (pipe.position1.X <= -180)
                {
                    unusedPipes.Add(pipe);
                }
            }
    
            foreach (var unusedPipe in unusedPipes)
            {
                pipes.Remove(unusedPipe);
            }
    

    我只是猜测管道是管道列表。

    【讨论】:

    • “删除”会有问题(变量超出范围)。您需要枚举未使用的Pipes 集合(如其他答案中所示)。
    • 是的,RemoveAll 也没有像我想象的那样工作。更新的答案,谢谢对我投赞成票:)
    【解决方案5】:

    其他答案完全有效,将解决您的问题。

    但是,我认为您应该知道,您不需要创建第二个集合,只是为了在迭代期间删除一个对象。

    我们可以使用反向迭代从您的集合中移除一个对象。请尝试以下操作:

    foreach (Pipe pipe in pipes.Reverse<Pipe>())
    {
        pipes.Remove(pipe);
    }
    

    上面的代码假设你的集合是一个通用列表:

    List<Pipe> pipes = new List<Pipe>();
    

    编辑

    这是我目前正在开发的游戏的示例:

    private List<Debris> listOfDebris = new List<Debris>();
    
    foreach (Debris debris in listOfDebris.Reverse<Debris>())
    {
        CalculateDebrisLocation(debris);
    
        // Remove debris if it travels outside level boundries
        if (!debris.Rect.Intersects(currentLevel.MapRect))
            listOfDebris.Remove(debris);
    }
    

    【讨论】:

    • 你确定吗?我能找到的所有其他反向迭代示例都是在执行此操作时使用 for 循环(而不是 foreach),而当您在其中执行删除/添加时,foreach 会变得非常不安。
    • 是的,我很确定!我编辑了我的答案,向您展示了我使用反向迭代的一种情况。如果您认为它有什么问题,请告诉我(据我所知,效果很好!)
    • 如果你有一个工作样本,我相信你:)。总是很高兴能学到新东西,+1!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-04
    • 2020-01-27
    • 1970-01-01
    • 1970-01-01
    • 2015-06-10
    • 1970-01-01
    相关资源
    最近更新 更多