Як переглядати словник та змінювати значення?


76
Dictionary<string,double> myDict = new Dictionary();
//...
foreach (KeyValuePair<string,double> kvp in myDict)
 {
     kvp.Value = Math.Round(kvp.Value, 3);
}

Я отримую повідомлення про помилку: "Властивість або індексатор 'System.Collections.Generic.KeyValuePair.Value' неможливо призначити - це лише для читання."
Як я можу переглядати myDictта змінювати значення?


1
Якщо додавання великої кількості довідкових об'єктів є прийнятним з точки зору продуктивності (опосередкованість та більше gc-тиску), і ви маєте контроль над словником, тоді створення вікна для ваших значень вирішить проблему, наприклад: class Box<T> { public T boxed; }а потім скористайтеся Dictionary<string, Box<double>>яким «змінити» в Еогеасп що - щось на кшталт: kvp.Value.boxed = 123.456;. Не кажучи, що це "найкращий" підхід, але він має свою частку використання.
AnorZaken,

Це відповідає на ваше запитання? Редагування значень словника у циклі foreach
Малкольм

Відповіді:


111

Згідно з MSDN :

Заява foreach - це обгортка навколо перелічувача, яка дозволяє лише читати з колекції, а не писати в неї.

Використовуй це:

var dictionary = new Dictionary<string, double>();
// TODO Populate your dictionary here
var keys = new List<string>(dictionary.Keys);
foreach (string key in keys)
{
   dictionary[key] = Math.Round(dictionary[key], 3);
}

7
Я протестував ваш приклад у .NET 2 та 3.5, і він видав "Колекція була зміненим винятком". Дивіться: stackoverflow.com/questions/1562729/... Чи змінилося це в .NET 4 чи ви не протестували свій приклад?
Ash

3
Як ніяково - я залишив ту частину, де заповнюється список. Виправлено зараз. Ідея тут полягає в тому, що ви можете змінити значення словникового запису, а не його посилання.
Джастін Р.

Бажаю, щоб ви залишили рядок коментаря до запитання з "// ... (заповнити)". На перший погляд список порожній.
crokusek

1
До речі, ви можете просто зробити словник.
Key.ToList

38

Для ледачих програмістів:

Dictionary<string, double> dictionary = new Dictionary<string, double>();
foreach (var key in dictionary.Keys.ToList())
{
   dictionary[key] = Math.Round(dictionary[key], 3);
}

2
Оскільки я зараз кодую .NET 4.5, ToList()метод недоступний, але Keysчлен є ітерабельним, тому .ToList()непотрібний.
Mike C

4
@MikeC см коментар на stackoverflow.com/a/2260462/1037948 - ви не можете безпосередньо на перерахування ключів, то .ToList()є «зламати» , щоб обійти це
drzaus

3
ToList () - це метод розширення LINQ. Він повинен бути доступним, якщо ви додасте "using System.Linq;" до ваших використаних тверджень.
bcwhims

@drzaus: Чому ви не можете перерахувати ключі? Ключі - це словник, який можна перелічити, як і будь-який інший словник. Я роблю це без .ToList ().
Veverke

7
Я знаю, що це стара відповідь, але я вважаю за краще прийняту - я б сказав, що замість того, щоб бути лінивим, він є більш стислим (вирізає рядок, явно декларуючи список ключів). Отже, щоб відповісти на вищезазначене запитання: ви можете перерахувати колекцію ключів, але питання стосується переліку та внесення змін, чого ви не можете зробити. Додавання списку ToList () означає, що ви насправді перераховуєте список, який, як видається, містить ті самі об'єкти, що й ключі у словнику. Це залишає сам словник змінним, що дозволяє вносити зміни.
Кейт

8

Не слід змінювати словник під час ітерації, інакше ви отримаєте виняток.

Отже, спочатку скопіюйте пари ключ-значення в тимчасовий список, а потім перегляньте цей тимчасовий список, а потім змініть свій словник:

Dictionary<string, double> myDict = new Dictionary<string, double>();

// a few values to play with
myDict["a"] = 2.200001;
myDict["b"] = 77777.3333;
myDict["c"] = 2.3459999999;

// prepare the temp list
List<KeyValuePair<string, double>> list = new List<KeyValuePair<string, double>>(myDict);

// iterate through the list and then change the dictionary object
foreach (KeyValuePair<string, double> kvp in list)
{
    myDict[kvp.Key] = Math.Round(kvp.Value, 3);
}


// print the output
foreach (var pair in myDict)
{
    Console.WriteLine(pair.Key + " = " + pair.Value);
}

// uncomment if needed
// Console.ReadLine();

вихід (на моїй машині):

a = 2,2
b = 77777,333
c = 2,346

Примітка : з точки зору продуктивності, це рішення трохи краще, ніж опубліковані на даний момент рішення, оскільки значення вже присвоєне ключем, і немає необхідності знову отримувати його з об’єкта словника.


1
Це, мабуть, такий підхід, якого я дотримуватимусь, але я хотів би знати накладні витрати на копіювання повного словника.
Альберто

4

минув якийсь час, але, можливо, когось це цікавить:

yourDict = yourDict.ToDictionary(kv => kv.Key, kv => Math.Round(kv.Value, 3))

Дуже читабельна версія! Можливо, стане трохи захаращеним при додаванні перевірок на помилки.
Csharpest

2

Я помітив, що найшвидший (на даний момент) перегляд словника з модифікацією:

//Just a dumb class
class Test<T>
{
    public T value;

    public Test() { }
    public Test(T v) { value = v; }
}

Dictionary<int, Test<object>> dic = new Dictionary<int, Test<object>>();
//Init dictionary
foreach (KeyValuePair<int, Test> pair in dic)
{
    pair.Value.value = TheObject;//Modify
}

VS

List<int> keys = new List<int>(dic.Keys); //This is fast operation   
foreach (int key in keys)
{
    dic[key] = TheObject;
}

Перший займає близько 2,2 с, а другий 4,5 с (перевірений розмір словника 1000 і повторений 10 тис. Разів, зміна розміру словника на 10 не змінило співвідношення). Також не було великої проблеми з отриманням списку ключів, значення get [key] словника - це просто повільна VS, вбудована в ітерацію. Крім того, якщо ви хочете ще більшої швидкості, використовуйте жорстко закодований тип для німого ("Тест") класу, при цьому я отримав його приблизно 1,85 с (з жорстким кодом до "об'єкта").

РЕДАГУВАТИ:

Анна раніше розміщувала те саме рішення: https://stackoverflow.com/a/6515474/766304


1

Одним із рішень було б заздалегідь помістити ключі до списку (або іншої колекції) та переглядати їх, змінюючи словник:

Dictionary<string, double> dictionary = new Dictionary<string, double>();

// Populate it
List<string> keys = new List<string>(dictionary.Keys);

foreach (string key in keys)
{
   dictionary[key] = Math.Round(dictionary[key], 3);
}

-1

Хоча перегляд словника безпосередньо неможливий, оскільки ви отримуєте виняток (як уже говорив Рон), вам не потрібно використовувати тимчасовий список для вирішення проблеми.

Натомість використовуйте не цикл foreach, а forцикл, щоб переглядати словник та змінювати значення з індексованим доступом:

Dictionary<string, double> myDict = new Dictionary<string,double>();
//...    
for(int i = 0; i < myDict.Count; i++) {
    myDict[myDict.ElementAt(i).Key] = Math.Round(myDict.ElementAt(i).Value, 3);
}

Незважаючи на те, що це буде працювати, Enumerable.ElementAt()- що метод розширення ви використовуєте - це O(n)операція для НЕ IList<>з .
Євген Березовський

Скаже прямо, що говорить Євген Бересовський. У підсумку це принаймні O (n ^ 2). Отже, це дуже погане рішення.
Павло Чикулаєв

-2

Переглядайте ключі у словнику, а не KeyValuePairs.

Dictionary<string, double> myDict = new Dictionary<string, double>();
//...
foreach (string key in myDict.Keys)
{
    myDict[key] = Math.Round(myDict[key], 3);
}

9
На диво, це викликає виняток " Колекція змінена ... ".
Слаума 06.03.14

@Slauma Дійсно , як не дивно , оскільки ця зміна не є структурною зміною, тому кидання перечислювача насправді не було б необхідним.
Євген Березовський
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.