假设我有一个类似的更新函数:
for (int i = 0; i < players.Count; i++)
{
Debug.Log(players[i].name);
}
这个列表正在Update((中使用,所以如果我从另一个脚本/函数中删除任何列表项,它只会抛出一个错误,因为循环进行时列表大小发生了变化。我该如何解决这个问题?
这个bug在编程中的任何地方都会弹出,因为你正在处理一些可能会修改你正在处理的集合的集合。
根据我的经验,有两种选择:
- 如果可以显示过期信息,请复制该集合并使用该副本
- 处理正在修改的集合中发生的任何错误,并使用最新的集合重新启动逻辑
选项1:使用副本:
在访问集合之前,请复制内存中的内容。例如,使用.ToList()
或.ToArray()
var playersCopy = players.ToList();
for (int i = 0; i < playersCopy .Count; i++)
{
Debug.Log(playersCopy [i].name);
}
使用这种方法,您在内存中有一个无法修改的副本,即使认为该副本可能已经过时或过时。
2( 。如果使用过时的副本是不可接受的,那么您需要在迭代时处理正在修改的集合中发生的异常。
例如,您可以将对集合的访问权放在try catch
中,并在修改集合时重试逻辑。
bool success = false;
while(!success)
{
try {
{
for (int i = 0; i < players.Count; i++)
{
Debug.Log(players[i].name);
}
success=true; // loop completed without error
}
catch { }
}
请注意,后面是一个简化的示例,根据您的情况,您可能需要对捕获的错误执行不同的操作。
还要注意,如果有副作用,则不建议使用后者。例如,Debug.Log
可能已经打印出了一个过时的播放器。
这有自己的解决方案,例如:先将玩家名称保存到列表中,然后打印出内存中的列表,或者再次使用自定义逻辑(例如,跟踪哪些玩家不再在列表中,并打印一条消息,说明他们离开了,然后继续列表的其余部分(。
方法仍然是一样的,使用内存中的副本或实时检测更改并进行处理。
幸运的是,根据您提供的信息,这不会引发错误。
在标准场景中,在调用以下脚本上的下一个Update方法之前,将调用并完成Update方法。主Unity工作线程不是从其他地方调用的,所以一个脚本不会在函数中间中断Update方法调用。
现在,如果你说你正在使用协同例程,并且在"foreach"循环中让步了,那么在这种情况下,你可能会遇到枚举器抛出错误的情况,因为你可以从这个脚本或其他脚本中的其他地方修改底层的可枚举(播放器(。
另一种说法是,这很糟糕:
public class Base : MonoBehaviour
{
List<int> list;
void Start ( )
{
list = new List<int> { 1, 2, 3, 4 };
StartCoroutine ( ForEachTest ( ) );
list.Add ( 5 );
}
IEnumerator ForEachTest ( )
{
foreach ( var item in list )
{
Debug.Log ( $"Item value : {item}" );
yield return null;
}
}
}
将导致:
InvalidOperationException:集合已修改;枚举操作可能不会执行。
如果您查看枚举器的代码,您可以了解引发异常的原因。
public bool MoveNext()
{
List<T> list = this.list;
if (version == list._version && (uint)index < (uint)list._size)
{
current = list._items[index];
index++;
return true;
}
return MoveNextRare();
}
private bool MoveNextRare()
{
if (version != list._version)
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
index = list._size + 1;
current = default(T);
return false;
}
当修改List(如您使用Count而非Length所建议的(时,枚举器会看到集合的版本已被修改,从而使枚举器无效。
请注意,如果您不使用枚举器,而只是使用检查列表的Count属性的for循环,则不会遇到此问题。