【问题标题】: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&lt;string, WrappedValue&lt;int&gt;&gt;... 遍历键/值对,并在包装​​器中更改值 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 循环后将它与旧字典交换。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2021-04-14
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2022-01-02
              • 2021-10-13
              • 1970-01-01
              相关资源
              最近更新 更多