C # - изменить значение пары ключ-значение словаря в то время как в foreach - PullRequest
9 голосов
/ 23 февраля 2011

У меня есть этот код, который запускается каждый кадр игры:

    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, не копируя Словарь в каждом кадре?

Ответы [ 6 ]

13 голосов
/ 23 февраля 2011

Я не советую пытаться изменить коллекцию, просматривая ее со словарями, однако это возможно, поскольку прямой доступ к ключу доступен.Просто добавьте .ToArray() после conditionTimes.Keys в foreach, тогда ключи станут отдельной коллекцией, и вы можете изменить словарь:

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;
    }
}

Вы также должны изменить свой код, чтобы, еслипри изменении ключа вы фактически удаляете запись из своего словаря и добавляете новую, поскольку ключи действительно не могут быть изменены, просто удалены.
Опять же, это не рекомендуется.

3 голосов
/ 23 февраля 2011

Нет, вы не можете.Есть одна опция противный , которую вы можете использовать:

public class Wrapper<T>
{
    public T WrappedValue { get; set; }

    // *Maybe* add implicit conversions here? Icky...
}

Затем вы создадите (скажем) Dictionary<string, WrappedValue<int>> ... итерацию по парам ключ / значение и изменитезначение в обертке, а не в самой записи ссылается на другую обертку.

Хотя я не думаю, что рекомендую это - это будет неудобно и легко неправильное использование .

Другой вариант, если вы используете .NET 4, это использовать ConcurrentDictionary, что позволяет разрешать одновременные изменения.

0 голосов
/ 23 февраля 2011

Обычный подход - запоминать ключи, которые вы хотите изменить в первом цикле, а затем иметь секунду, которая перебирает запомненные ключи и изменяет исходный словарь.Это позволяет избежать создания совершенно нового словаря со всеми элементами.

0 голосов
/ 23 февраля 2011

Вы не можете изменять коллекцию, перечисляя ее внутри оператора 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...
}

Конечно, в параллельной среде вы должны убедиться, что словарь и список синхронизированы ...

0 голосов
/ 23 февраля 2011

вы должны использовать другой шаблон, чтобы делать то, что вы пытаетесь сделать, потому что для каждого из них вы не можете изменить перечислитель, к которому вы подключаетесь.Представьте, что если вы запускаете foreach в отсортированном списке с самого начала, вы начинаете обрабатывать элемент с ключом = "A", затем переходите к "B", затем вы меняете "C" на "B", что произойдет?Ваш список восстанавливается, и вы больше не знаете, что зацикливаете и где находитесь.

в общем, вы "можете" сделать это с помощью for (int i = dictionary.count-1; i> =0; --i) или что-то в этом роде, но это также зависит от вашего контекста, я бы действительно попробовал использовать другой подход.

0 голосов
/ 23 февраля 2011

Извините, вы не можете.

Полагаю, вам лучше всего создать новый словарь, а затем заменить его старым, когда закончите цикл foreach.

...