【问题标题】:C# - Change value of dictionary key-value pair while in foreachC# - 在 foreach 中更改字典键值对的值
【发布时间】:2011-07-02 14:27:04
【问题描述】:
我有这段代码在游戏的每一帧都运行:
foreach (var repeaterAction in conditionTimes.Keys)
{
if (repeaterAction.Condition() == true)
{
if (conditionTimes[repeaterAction] == TimeSpan.Zero)
{
repeaterAction.Action();
}
else if (conditionTimes[repeaterAction] >= repeaterAction.InitialLapse)
{
repeaterAction.Action();
conditionTimes[repeaterAction] -= repeaterAction.ActionInterval;
}
conditionTimes[repeaterAction] += gameTime.ElapsedGameTime;
}
else
{
conditionTimes[repeaterAction] = TimeSpan.Zero;
}
}
这给了我以下错误:
集合已修改;枚举操作可能无法执行。
有没有办法在 foreach 循环内修改键值对中的值,而不需要每帧都复制字典?
【问题讨论】:
标签:
c#
collections
dictionary
foreach
enumeration
【解决方案1】:
我建议不要在使用字典循环遍历集合时尝试修改集合,但是这是可能的,因为可以使用直接键访问。只需在foreach 中的conditionTimes.Keys 之后添加.ToArray(),然后键就成为一个单独的集合,您可以修改字典:
foreach (var repeaterAction in conditionTimes.Keys.ToArray())
{
if (repeaterAction.Condition() == true)
{
if (conditionTimes[repeaterAction] == TimeSpan.Zero)
{
repeaterAction.Action();
}
else if (conditionTimes[repeaterAction] >= repeaterAction.InitialLapse)
{
repeaterAction.Action();
conditionTimes[repeaterAction] -= repeaterAction.ActionInterval;
}
conditionTimes[repeaterAction] += gameTime.ElapsedGameTime;
}
else
{
conditionTimes[repeaterAction] = TimeSpan.Zero;
}
}
您还必须修改您的代码,以便如果键更改,您实际上会从字典中删除一个条目并添加新条目,因为键确实无法更改,只是删除。
同样不建议这样做。
【解决方案2】:
在foreach 语句中枚举集合时,不能修改集合。所以你要么必须每次都复制它(你只需要复制密钥)。另一种选择是始终存储一个单独的键列表(例如,通过将字典封装在管理它的类中)。类似:
class MyDictionary<TKey, TValue>
{
private Dictionary<TKey, TValue> _dict = new Dictionary<TKey, TValue>();
private List<Keys> _keys = new List<TKey>();
public void Add(TKey key, TValue value)
{
_dict.Add(key, value);
_keys.Add(key);
}
//public bool Remove ...
//indexer...
}
当然,在并发环境中,您必须确保字典和列表同步...
【解决方案3】:
不,你不能。您可以使用一个 nasty 选项:
public class Wrapper<T>
{
public T WrappedValue { get; set; }
// *Maybe* add implicit conversions here? Icky...
}
然后您将创建(例如)Dictionary<string, WrappedValue<int>>... 遍历键/值对,并在包装器中更改值 within,而不是使条目本身引用不同的包装。
我不认为我会推荐这个 - 使用起来会很尴尬,而且很容易误用。
如果您使用 .NET 4,另一个选择是使用 ConcurrentDictionary,它确实允许并发修改。
【解决方案4】:
一种常见的方法是在第一个循环中记住要更改的键,然后让第二个循环遍历记住的键并更改原始字典。这样可以避免创建包含所有元素的全新字典。
【解决方案5】:
您应该使用另一种模式来执行您正在尝试执行的操作,因为 for each 不允许您更改循环到的枚举器。想象一下,如果您从一开始就在排序列表上运行 foreach,您开始处理带有 key="A" 的项目,然后转到“B”,然后将“C”更改为“B”,会发生什么?您的列表已被重新使用,您不再知道您在循环什么以及您在哪里。
一般来说,你“可以”用 for(int i=dictionary.count-1; i>=0; --i) 或类似的东西来做,但这也取决于你的上下文,我真的会尝试使用另一种方法。
【解决方案6】:
对不起,你不能。
我猜你最好的办法是创建一个新字典,然后在你完成 foreach 循环后将它与旧字典交换。