Как обновить хеш-таблицу С# в цикле?

Я пытаюсь обновить хэш-таблицу в цикле, но получаю сообщение об ошибке: System.InvalidOperationException: Коллекция была изменена; операция перечисления может не выполняться.

private Hashtable htSettings_m = new Hashtable();
htSettings_m.Add("SizeWidth", "728");
htSettings_m.Add("SizeHeight", "450");
string sKey = "";
string sValue = "";
foreach (DictionaryEntry deEntry in htSettings_m)
{
    // Get value from Registry and assign to sValue.
    // ...
    // Change value in hashtable.
    sKey = deEntry.Key.ToString();
    htSettings_m[sKey] = sValue;
}

Есть ли способ обойти это или, может быть, для этой цели есть лучшая структура данных?


person z-boss    schedule 28.11.2008    source источник
comment
Поверьте, что это дублирующий вопрос, см.: добавлять элементы в коллекцию при ее использовании"> stackoverflow.com/questions/287195/   -  person Gavin Miller    schedule 29.11.2008


Ответы (12)


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

        System.Collections.Hashtable ht = new System.Collections.Hashtable();

        ht.Add("test1", "test2");
        ht.Add("test3", "test4");

        List<string> keys = new List<string>();
        foreach (System.Collections.DictionaryEntry de in ht)
            keys.Add(de.Key.ToString());

        foreach(string key in keys)
        {
            ht[key] = DateTime.Now;
            Console.WriteLine(ht[key]);
        }
person keithwarren7    schedule 28.11.2008

По идее я бы сделал:

Hashtable table = new Hashtable(); // ps, I would prefer the generic dictionary..
Hashtable updates = new Hashtable();

foreach (DictionaryEntry entry in table)
{
   // logic if something needs to change or nog
   if (needsUpdate)
   {
      updates.Add(key, newValue);
   }
}

// now do the actual update
foreach (DictionaryEntry upd in updates)
{
   table[upd.Key] = upd.Value;
}
person Davy Landman    schedule 28.11.2008

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

foreach (string key in new List<string>(dictionary.Keys))

Почему вы получаете исключение, говорящее вам, что вы изменили коллекцию, которую перебираете, хотя на самом деле это не так?

Внутри класса Hashtable есть поле версии. Методы Add, Insert и Remove увеличивают эту версию. Когда вы создаете перечислитель в любой из коллекций, предоставляемых Hashtable, объект перечислителя включает текущую версию Hashtable. Метод MoveNext перечислителя сравнивает версию перечислителя с версией Hashtable и, если они не равны, выдает InvalidOperationException, которое вы видите.

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

В этом подходе есть еще один, более тонкий конструктивный дефект. Версия представляет собой Int32. Метод UpdateVersion не проверяет границы. Поэтому возможно, если вы сделаете точно нужное количество модификаций в хеш-таблице (2 раза Int32.MaxValue, плюс-минус), чтобы версия в хеш-таблице и в перечислителе были одинаковыми, даже если вы радикально изменили хэш-таблицу с момента создания. счетчик. Таким образом, метод MoveNext не будет генерировать исключение, хотя и должен, и вы получите неожиданные результаты.

person Robert Rossney    schedule 29.11.2008

Самый простой способ — скопировать ключи в отдельную коллекцию, а затем выполнить итерацию по ней.

Вы используете .NET 3.5? Если это так, LINQ немного упрощает задачу.

person Jon Skeet    schedule 28.11.2008

Ключевой частью является метод ToArray().

var dictionary = new Dictionary<string, string>();
foreach(var key in dictionary.Keys.ToArray())
{
    dictionary[key] = "new value";
}
person Gian Marco    schedule 19.03.2010
comment
гораздо более простое решение, чем текущее верхнее. - person Lars; 26.01.2013

Вы не можете изменить набор элементов, хранящихся в коллекции, во время ее перечисления, поскольку в большинстве случаев это очень усложняет жизнь итератору. Рассмотрим случай, когда коллекция представляет собой сбалансированное дерево и вполне может подвергаться ротации после вставки. У перечисления не было бы правдоподобного способа отслеживать то, что оно видело.

Однако, если вы просто пытаетесь обновить значение, вы можете написать:

deEntry.Value = sValue

Обновление значения здесь не влияет на перечислитель.

person Rob Walker    schedule 28.11.2008
comment
Это не компилируется: невозможно изменить члены «deEntry», потому что это «переменная итерации foreach» - person z-boss; 29.11.2008

Вот как я сделал это в словаре; сбрасывает каждое значение в dict на false:

Dictionary<string,bool> dict = new Dictionary<string,bool>();

for (int i = 0; i < dict.Count; i++)
{
    string key = dict.ElementAt(i).Key;
    dict[key] = false;
}
person Okan    schedule 12.08.2010

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

foreach (String sKey in htSettings_m.Keys)
{   // Get value from Registry and assign to sValue.
    // ...    
    // Change value in hashtable.
    htSettings_m[sKey] = sValue;
}

Другой вариант — создать новую HashTable. Повторяйте первый, добавляя элементы во второй, а затем заменяйте исходный новый.
Однако для циклического перебора ключей требуется меньше выделения объектов.

person pipTheGeek    schedule 28.11.2008

List<string> keyList = htSettings_m.Keys.Cast<string>().ToList();
foreach (string key in keyList) {

Это то же самое, что и другие ответы, но мне нравится одна строка, чтобы получить ключи.

person NateN    schedule 29.03.2013

Преобразуйте его в массив:

private Hashtable htSettings_m = new Hashtable();
htSettings_m.Add("SizeWidth", "728");
htSettings_m.Add("SizeHeight", "450");
string sKey = "";
string sValue = "";

ArrayList htSettings_ary = new ArrayList(htSettings_m.Keys)
foreach (DictionaryEntry deEntry in htSettings_ary)
{
    // Get value from Registry and assign to sValue.
    // ...
    // Change value in hashtable.
    sKey = deEntry.Key.ToString();
    htSettings_m[sKey] = sValue;
}
person Slogmeister Extraordinaire    schedule 11.03.2015

Может быть, вы можете использовать коллекцию Hashtable.Keys? Перечисление через это может быть возможно при изменении Hashtable. Но это только предположение...

person Vilx-    schedule 28.11.2008

person    schedule
comment
Это проблема ответа из неисправной памяти без предварительного тестирования. Я снова подумал об этом и вспомнил, что Hashtable использует один и тот же тип перечислителя для Hashtable и для его ключей. Серьезно ошибочная реализация, на мой взгляд. - person Stephen Martin; 29.11.2008
comment
Нет, проблема не в типе перечислителя, а в том, что свойство Keys не принимает копию всех ключей, а просто перебирает базовую коллекцию. Если вам нужно сделать копию, сделайте это явным образом. Это поведение, которое я хочу и ожидаю лично. - person Jon Skeet; 29.11.2008
comment
Я должен с вами не согласиться: коллекция Keys — это коллекция Keys, а не Hashtable, перечислитель Keys должен быть чувствителен к изменениям только в коллекции Keys, а не в Hashtable в целом. - person Stephen Martin; 29.11.2008
comment
Я думаю, что это дефект дизайна в классе Hashtable. Подробнее смотрите в моем ответе. - person Robert Rossney; 29.11.2008