Вопрос

Я пытаюсь обновить хеш-таблицу в цикле, но получаю сообщение об ошибке: 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;
}

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

Это было полезно?

Решение

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

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

Другие советы

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

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, чтобы избежать этого исключения:

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

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

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

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

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

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

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

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

var dictionary = new Dictionary<string, string>();
foreach(var key in dictionary.Keys.ToArray())
{
    dictionary[key] = "new value";
}

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

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

deEntry.Value = sValue

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

Вот как я это сделал в словаре; сбрасывает каждое значение в 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;
}

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

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

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

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;
}
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.Keys? Перечисление этого может быть возможным при изменении Hashtable. Но это только предположение ...

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top