Wie kann man das Wörterbuch durchlaufen und Werte ändern?


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

Ich erhalte die Fehlermeldung: "Eigenschaft oder Indexer 'System.Collections.Generic.KeyValuePair.Value' kann nicht zugewiesen werden - es ist schreibgeschützt."
Wie kann ich myDictWerte durchlaufen und ändern?


1
Wenn das Hinzufügen vieler Referenzobjekte aus Sicht der Leistung (Indirektion und mehr Gleichstromdruck) akzeptabel ist und Sie die Kontrolle über das Wörterbuch haben, würde das Erstellen eines Felds für Ihre Werte das Problem lösen, z. B.: class Box<T> { public T boxed; }Und dann ein verwenden, Dictionary<string, Box<double>>das Sie dann verwenden können "modifizieren" im foreach so etwas wie : kvp.Value.boxed = 123.456;. Nicht zu sagen, dass es der "beste" Ansatz ist, aber es hat seinen Anteil an Verwendungen.
AnorZaken

Antworten:


108

Laut MSDN :

Die foreach-Anweisung ist ein Wrapper um den Enumerator, mit dem nur aus der Sammlung gelesen und nicht geschrieben werden kann.

Benutze das:

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
Ich habe Ihr Beispiel in .NET 2 und 3.5 getestet und es wird die Ausnahme "Sammlung wurde geändert" ausgelöst. Siehe: stackoverflow.com/questions/1562729/… Hat sich dies in .NET 4 geändert oder haben Sie Ihr Beispiel nicht getestet?
Ash

3
Wie peinlich - ich habe den Teil weggelassen, in dem die Liste gefüllt ist. Jetzt behoben. Die Idee hier ist, dass Sie den Wert des Wörterbucheintrags ändern können, nur nicht seine Referenz.
Justin R.

Ich wünschte, Sie hätten die Fragekommentarzeile mit "// ... (Populate)" beibehalten. Auf einen Blick ist die Liste leer.
Crokusek

Übrigens können Sie einfach dictionary.Keys.ToList ()
Bojidar Stanchev

37

Für die faulen Programmierer:

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

2
Da ich jetzt mit .NET 4.5 codiere, ist die ToList()Methode nicht verfügbar, aber das KeysMitglied ist iterierbar und daher .ToList()nicht erforderlich .
Mike C

4
@ MikeC siehe Kommentar auf stackoverflow.com/a/2260462/1037948 - Sie können nicht direkt auf den Schlüsseln aufzählen, das .ToList()ist ein "Hack", um das zu
umgehen

3
ToList () ist eine LINQ-Erweiterungsmethode. Es sollte verfügbar sein, wenn Sie "using System.Linq;" hinzufügen. zu Ihren using-Anweisungen.
bcwhims

@drzaus: Warum kannst du nicht über Schlüssel aufzählen? Keys ist ein Wörterbuch, das wie jedes andere Wörterbuch aufgezählt werden kann. Ich mache es ohne .ToList ().
Veverke

7
Ich weiß, dass dies eine alte Antwort ist, aber ich ziehe sie der akzeptierten vor - ich würde sagen, anstatt faul zu sein, ist sie prägnanter (schneidet die Zeile aus, in der die Liste der Schlüssel explizit deklariert ist). Um die obige Frage zu beantworten: Sie können über die Schlüsselsammlung aufzählen, aber bei der Frage geht es um das Aufzählen und Vornehmen von Änderungen, was Sie nicht tun können. Das Hinzufügen von ToList () bedeutet, dass Sie tatsächlich eine Liste auflisten, die zufällig dieselben Objekte wie die Schlüssel im Wörterbuch enthält. Dadurch bleibt das Wörterbuch selbst veränderlich und Sie können Änderungen vornehmen.
Kate

8

Sie sollten das Wörterbuch nicht ändern, während Sie es wiederholen, da sonst eine Ausnahme auftritt.

Kopieren Sie also zuerst die Schlüssel-Wert-Paare in eine temporäre Liste, durchlaufen Sie diese temporäre Liste und ändern Sie dann Ihr Wörterbuch:

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();

Ausgabe (auf meinem Computer):

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

Hinweis : In Bezug auf die Leistung ist diese Lösung etwas besser als derzeit veröffentlichte Lösungen, da der Wert bereits mit dem Schlüssel zugewiesen wurde und nicht erneut aus dem Wörterbuchobjekt abgerufen werden muss.


1
Das ist wahrscheinlich der Ansatz, den ich verfolgen werde, aber ich würde gerne wissen, wie viel Aufwand das Kopieren des vollständigen Wörterbuchs kostet.
Alberto

4

einige Zeit vergangen, aber vielleicht interessiert sich jemand dafür:

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

Sehr gut lesbare Version! Kann beim Hinzufügen von Fehlerprüfungen etwas unübersichtlich werden.
Csharpest

2

Ich habe festgestellt, dass der schnellste Weg (in diesem Moment) über das Wörterbuch mit Änderung iteriert:

//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;
}

Das erste dauert ungefähr 2,2 Sekunden und das zweite 4,5 Sekunden (getestete Wörterbuchgröße von 1000 und wiederholte 10.000-malige Zeit, das Ändern der Wörterbuchgröße auf 10 hat die Verhältnisse nicht geändert). Es gab auch keine große Sache mit dem Abrufen der Schlüsselliste. Der Wert des Wörterbuchs [Schlüssel] ist nur langsam, VS in Iteration eingebaut. Auch wenn Sie noch mehr Geschwindigkeit wollen, verwenden Sie einen hartcodierten Typ für eine dumme Klasse ("Test"). Damit habe ich ungefähr 1,85 Sekunden (mit einem hartcodierten Typ für "Objekt").

BEARBEITEN:

Anna hat dieselbe Lösung bereits veröffentlicht: https://stackoverflow.com/a/6515474/766304


1

Eine Lösung wäre, die Schlüssel vorher in eine Liste (oder eine andere Sammlung) aufzunehmen und sie zu durchlaufen, während Sie das Wörterbuch ändern:

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

Während das direkte Durchlaufen des Wörterbuchs nicht möglich ist, weil Sie eine Ausnahme erhalten (wie Ron bereits sagte), müssen Sie keine temporäre Liste verwenden, um das Problem zu lösen.

Verwenden Sie stattdessen nicht die Schleife foreach, sondern eine forSchleife, um das Wörterbuch zu durchlaufen und die Werte mit indiziertem Zugriff zu ändern:

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

Obwohl dies funktioniert, Enumerable.ElementAt()handelt es sich bei der von Ihnen verwendeten Erweiterungsmethode um eine O(n)Operation für Nicht- IList<>Benutzer .
Evgeniy Berezovsky

Ich werde explizit sagen, was Eugene Beresovsky sagt. Dies ist mindestens O (n ^ 2). Das ist also eine sehr schlechte Lösung.
Pavel Chikulaev

-2

Durchlaufen Sie die Schlüssel im Wörterbuch, nicht die KeyValuePairs.

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

9
Überraschenderweise wird eine Ausnahme " Sammlung geändert ... " ausgelöst.
Slauma

@Slauma In der Tat überraschend , da diese Änderung keine strukturelle Änderung ist, wäre es in der Tat nicht notwendig, den Enumerator-Wurf zu haben.
Evgeniy Berezovsky
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.