Как обновить хэш-таблицу C # в цикле? - PullRequest
10 голосов
/ 29 ноября 2008

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

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

Ответы [ 12 ]

14 голосов
/ 29 ноября 2008

вы можете сначала прочитать коллекцию ключей в другом экземпляре 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]);
        }
4 голосов
/ 29 ноября 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;
}
3 голосов
/ 29 ноября 2008

Если вы используете словарь вместо 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, дай или возьми), чтобы версия в Hashtable и перечислителе была одинаковой, даже если вы радикально изменили Hashtable с момента создания перечислителя. Таким образом, метод MoveNext не будет генерировать исключение, даже если он должен, и вы получите неожиданные результаты.

2 голосов
/ 19 марта 2010

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

var dictionary = new Dictionary<string, string>();
foreach(var key in dictionary.Keys.ToArray())
{
    dictionary[key] = "new value";
}
2 голосов
/ 29 ноября 2008

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

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

1 голос
/ 29 ноября 2008

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

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

deEntry.Value = sValue

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

0 голосов
/ 11 марта 2015

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

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;
}
0 голосов
/ 29 марта 2013
List<string> keyList = htSettings_m.Keys.Cast<string>().ToList();
foreach (string key in keyList) {

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

0 голосов
/ 12 августа 2010

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

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

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

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

...