29 Stimmen

Warum kann man die Werte eines Wörterbuchs nicht ändern, während man seine Schlüssel aufzählt?

class Program
{
    static void Main(string[] args)
    {
        var dictionary = new Dictionary<string, int>()
        {
            {"1", 1}, {"2", 2}, {"3", 3}
        };

        foreach (var s in dictionary.Keys)
        {
            // Throws the "Collection was modified exception..." on the next iteration
            // What's up with that?

            dictionary[s] = 1;  
        }
    }
}

Ich verstehe vollkommen, warum diese Ausnahme beim Aufzählen einer Liste ausgelöst wird. Es scheint vernünftig zu erwarten, dass sich während der Aufzählung die Struktur des aufgezählten Objekts nicht ändert. Ändert sich jedoch eine Wert eines Wörterbuchs ändert auch dessen Struktur ? Insbesondere die Struktur der Schlüssel?

21voto

JaredPar Punkte 699699

Denn die Werte und Schlüssel werden als Paar gespeichert. Es gibt keine separate Struktur für Schlüssel und Werte, sondern eine einzige Struktur, in der beide als Wertepaar gespeichert werden. Wenn Sie einen Wert ändern, müssen Sie die zugrunde liegende Struktur ändern, die sowohl Schlüssel als auch Werte enthält.

Ändert die Änderung eines Wertes zwangsläufig die Reihenfolge der zugrunde liegenden Struktur? Nein. Dies ist jedoch ein implementierungsspezifisches Detail und die Dictionary<TKey,TValue> Klasse, die richtigerweise davon ausging, dies nicht zu verraten, indem sie die Änderung von Werten als Teil der API zuließ.

0 Stimmen

Richtige Antwort, die Elemente sind vom Typ KeyValuePair<TKey, TValue>, der Enumerator iteriert die Elemente, nicht die Schlüssel.

0 Stimmen

Soweit ich weiß, ist ein Wörterbuch als Hash-Tabelle implementiert, d.h. es wird nicht als Liste von Schlüssel-Wert-Paaren gespeichert. Und selbst wenn es so wäre, ist der Wert nur ein Parasit, der sich an den Schlüssel hängt - es sind die Schlüssel, die in einer komplizierten, ausgeklügelten Struktur organisiert sind.

0 Stimmen

@Vitaliy Ich habe in meiner Antwort aus gutem Grund nicht "Liste" gesagt. Die zugrundeliegende Strukturimplementierung ist ziemlich irrelevant, außer dass Schlüssel und Werte als ein Paar betrachtet werden. Die Änderung des einen wirkt sich auf den anderen aus, da sie als Teil desselben Objekts betrachtet werden.

8voto

StriplingWarrior Punkte 141402

Ich weiß, worauf Sie hinauswollen. Was die meisten Antworten hier nicht beachten, ist, dass Sie über die Liste der Schlüssel iterieren und nicht über die Elemente des Wörterbuchs selbst. Wenn die Programmierer des .NET-Frameworks es wollten, könnten sie relativ leicht zwischen Änderungen an der Struktur des Wörterbuchs und Änderungen an den Werten im Wörterbuch unterscheiden. Aber selbst wenn man die Schlüssel einer Auflistung durchläuft, holt man in der Regel am Ende die Werte trotzdem heraus. Ich vermute, dass die Entwickler des .NET-Frameworks sich gedacht haben, dass man bei einer Iteration über diese Werte wissen möchte, ob sie unter einem geändert werden, genauso wie bei jeder Liste. Entweder das, oder sie hielten es nicht für wichtig genug, um die Programmierung und Wartung zu rechtfertigen, die erforderlich wäre, um zwischen einer Art von Änderung und einer anderen zu unterscheiden.

0 Stimmen

Danke für den Hinweis. Auch wenn dies eine alte Antwort ist, möchte ich noch auf etwas hinweisen. Das Verwirrendste ist, dass man im VS-Debugger in Object.Dictionary.List bohren kann (ich hoffe, ich habe es klar genug beschrieben) und dort dieselbe Liste von Werten sieht, wie in Object.Dictionary.Items. Kein Wunder, dass wir dann dazu neigen, die falsche Sammlung zu durchlaufen...

8voto

Dolphin Punkte 4567

Dank Vitaliy bin ich zurückgegangen und habe mir den Code genauer angesehen, und es sieht so aus, als ob es eine spezifische Implementierungsentscheidung ist, dies zu verbieten (siehe Schnipsel unten). Das Dictionary speichert einen privaten Wert namens verrsion, der erhöht wird, wenn der Wert eines vorhandenen Elements geändert wird. Wenn der Enumerator erstellt wird, notiert er den Wert zu diesem Zeitpunkt und überprüft ihn dann bei jedem Aufruf von MoveNext.

for (int i = this.buckets[index]; i >= 0; i = this.entries[i].next)
{
    if ((this.entries[i].hashCode == num) && this.comparer.Equals(this.entries[i].key, key))
    {
        if (add)
        {
            ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AddingDuplicate);
        }
        this.entries[i].value = value;
        this.version++;
        return;
    }
}

Ich wüsste keinen Grund, warum dies notwendig sein sollte. Sie können die Eigenschaften des Wertes immer noch ändern, nur nicht einem neuen Wert zuweisen:

public class IntWrapper
{
  public IntWrapper(int v) { Value = v; }
  public int Value { get; set; }
}

class Program
{
  static void Main(string[] args)
  {
    var kvp = new KeyValuePair<string, int>("1",1);
    kvp.Value = 17;
    var dictionary = new Dictionary<string, IntWrapper>(){
      {"1", new IntWrapper(1)}, 
      {"2", new IntWrapper(2)}, 
      {"3", new IntWrapper(3)} };

    foreach (var s in dictionary.Keys)
    {
      dictionary[s].Value = 1;  //OK
      dictionary[s] = new IntWrapper(1); // boom
    }
  } 
}

0 Stimmen

Ich bin skeptisch, dass ein Wörterbuch mit einem Array von KeyValue-Paaren implementiert wird. Insbesondere in Anbetracht der Tatsache, dass zwei verschiedene Schlüssel demselben numerischen Hash-Code zugeordnet werden können. Jeder Schlüssel ist also tatsächlich mehreren Werten zugeordnet. Daher die Notwendigkeit, Equals außer Kraft zu setzen.

0 Stimmen

Wenn Sie skeptisch sind, können Sie sich eine Kopie von .Net Reflector besorgen und den Code selbst disassemblieren.

3 Stimmen

Nun, ich habe mir die Implementierung angesehen, und obwohl ein Wörterbuch eine Sammlung von Einträgen hat, ist es nicht offensichtlich, warum der Wert nicht geändert werden kann. Obwohl ein KeyValuePair eine Struktur ist, ist es nicht von Natur aus unveränderlich, wie Sie erklärt haben, so dass seine Eigenschaft Wert geändert werden kann. Darüber hinaus in Anbetracht der Grund, dass mit der gegebenen Implementierung wirft, wenn ein Wert während der Aufzählung geändert wird, Dieses Verhalten ist nicht intuitiv und bricht Kapselung durch die Offenlegung der Implementierung Details. Einige würden sagen, das ist einfach nur schlechtes API-Design!

5voto

John Kugelman Punkte 327535

Es ist möglich, dass Sie gerade einen neuen Schlüssel in das Wörterbuch eingefügt haben, was in der Tat die dictionary.Keys . Auch wenn das in dieser speziellen Schleife nie passieren wird, ist die [] Operation kann im Allgemeinen die Liste der Schlüssel ändern, so dass dies als Mutation gekennzeichnet wird.

5voto

Pavel Minaev Punkte 97251

Indexer auf Dictionary ist potenziell eine Operation, die die Struktur der Sammlung verändern kann, da sie einen neuen Eintrag mit einem solchen Schlüssel hinzufügt, wenn noch keiner vorhanden ist. Dies ist hier offensichtlich nicht der Fall, aber ich erwarte, dass Dictionary Vertrag ist bewusst einfach gehalten, indem alle Operationen auf dem Objekt in "mutierend" und "nicht mutierend" unterteilt werden und alle "mutierenden" Operationen Enumeratoren ungültig machen, auch wenn sie eigentlich nichts ändern.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X