Я пытаюсь обновить хэш-таблицу в цикле, но получаю сообщение об ошибке: 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;
}
Есть ли способ обойти это или, может быть, есть лучшая структура данных для этой цели?





Вы не можете изменить набор элементов, хранящихся в коллекции, во время перечисления по ней, поскольку в большинстве случаев это очень усложняет жизнь итератору. Рассмотрим случай, когда коллекция представляет собой сбалансированное дерево и может претерпевать вращения после вставки. У счетчика не было бы надежного способа отслеживать то, что он видел.
Однако, если вы просто пытаетесь обновить значение, вы можете написать:
deEntry.Value = sValue
Обновление значения здесь не влияет на счетчик.
Это не компилируется: нельзя изменять члены deEntry, потому что это «переменная итерации foreach»
По идее я бы сделал:
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;
}
Может быть, вы можете использовать коллекцию Hashtable.Keys? Перечисление через это может быть возможно при изменении Hashtable. Но это только предположение ...
вы можете сначала прочитать коллекцию ключей в другом экземпляре 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]);
}
private Hashtable htSettings_m = new Hashtable();
htSettings_m.Add("SizeWidth", "728");
htSettings_m.Add("SizeHeight", "450");
string sValue = "";
foreach (string sKey in htSettings_m.Keys)
{
// Get value from Registry and assign to sValue
// ...
// Change value in hashtable.
htSettings_m[sKey] = sValue;
}
Это проблема с ответом на неисправную память без предварительного тестирования. Я снова подумал об этом и вспомнил, что Hashtable использует один и тот же тип перечислителя для Hashtable и для его ключей. На мой взгляд, реализация с серьезными недостатками.
Нет, проблема не в типе перечислителя - а в том, что свойство Keys не принимает копировать всех ключей, оно просто выполняет итерацию по базовой коллекции. Когда вы необходимость, чтобы сделать копию, сделайте это явно. Я лично хочу и ожидаю именно такого поведения.
Я должен с вами не согласиться: коллекция Keys - это коллекция Keys, а не Hashtable, перечислитель Keys должен быть чувствителен к изменениям только в коллекции Keys, а не в Hashtable в целом.
Я думаю, что это дефект конструкции класса Hashtable. См. Подробности в моем ответе.
Это зависит от того, почему вы перебираете элементы в хеш-таблице. Но вместо этого вы, вероятно, сможете перебирать ключи. Так
foreach (String sKey in htSettings_m.Keys)
{ // Get value from Registry and assign to sValue.
// ...
// Change value in hashtable.
htSettings_m[sKey] = sValue;
}
Другой вариант - создать новую HashTable. Пройдите по первому, добавляя элементы ко второму, а затем замените оригинал новым. Однако перебор ключей требует меньшего выделения объектов.
Самый простой способ - скопировать ключи в отдельную коллекцию, а затем выполнить итерацию по ней.
Вы используете .NET 3.5? Если это так, LINQ немного упрощает задачу.
Если вы используете Dictionary вместо Hashtable, чтобы известен тип ключей, самый простой способ сделать копию коллекции Keys, чтобы избежать этого исключения:
foreach (string key in new List<string>(dictionary.Keys))
Почему вы получаете исключение, сообщающее вам, что вы изменили коллекцию, которую повторяете, когда на самом деле это не так?
Внутри класса Hashtable есть поле версии. Методы Add, Insert и Remove увеличивают эту версию. Когда вы создаете перечислитель для любой из коллекций, которые предоставляет Hashtable, объект перечислителя включает текущую версию Hashtable. Метод MoveNext перечислителя проверяет версию перечислителя на соответствие с Hashtable и, если они не равны, генерирует InvalidOperationException, которое вы видите.
Это очень простой механизм для определения того, была ли изменена хеш-таблица. На самом деле это слишком просто. Коллекция Keys действительно должна поддерживать свою собственную версию, а ее метод GetEnumerator должен сохранять версию коллекции в перечислителе, а не версию Hashtable.
В этом подходе есть еще один, более тонкий недостаток дизайна. Версия - Int32. Метод UpdateVersion не проверяет границы. Следовательно, возможно, если вы сделаете точно нужное количество модификаций в Hashtable (2 раза Int32.MaxValue, плюс-минус), чтобы версия в Hashtable и перечислитель были одинаковыми, даже если вы радикально изменили Hashtable с момента создания счетчик. Таким образом, метод MoveNext не будет генерировать исключение, даже если должен, и вы получите неожиданные результаты.
Ключевой частью является метод ToArray ()
var dictionary = new Dictionary<string, string>();
foreach(var key in dictionary.Keys.ToArray())
{
dictionary[key] = "new value";
}
намного более простое решение, чем текущее топовое.
Вот как я это сделал в словаре; сбрасывает каждое значение в 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;
}
List<string> keyList = htSettings_m.Keys.Cast<string>().ToList();
foreach (string key in keyList) {
Это то же самое, что и другие ответы, но мне нравится одна строка, чтобы получить ключи.
Преобразуйте его в массив:
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;
}
Считайте, что это дублированный вопрос, см .: stackoverflow.com/questions/287195/…