Was ist der beste Weg, um ein generisches .NET-Wörterbuch <string, T> zu klonen / zu kopieren?

211

Ich habe ein allgemeines Wörterbuch Dictionary<string, T>, mit dem ich im Wesentlichen einen Klon () aus allen Vorschlägen erstellen möchte.

mikeymo
quelle

Antworten:

184

Okay, das .NET 2.0 antwortet:

Wenn Sie die Werte nicht klonen müssen, können Sie die Konstruktorüberladung für Dictionary verwenden, die ein vorhandenes IDictionary verwendet. (Sie können den Komparator auch als Komparator des vorhandenen Wörterbuchs angeben.)

Wenn Sie Sie die Werte müssen klonen, können Sie so etwas wie folgt verwenden:

public static Dictionary<TKey, TValue> CloneDictionaryCloningValues<TKey, TValue>
   (Dictionary<TKey, TValue> original) where TValue : ICloneable
{
    Dictionary<TKey, TValue> ret = new Dictionary<TKey, TValue>(original.Count,
                                                            original.Comparer);
    foreach (KeyValuePair<TKey, TValue> entry in original)
    {
        ret.Add(entry.Key, (TValue) entry.Value.Clone());
    }
    return ret;
}

Das hängt TValue.Clone()natürlich auch davon ab, ein angemessen tiefer Klon zu sein.

Jon Skeet
quelle
Ich denke, das macht nur eine flache Kopie der Wörterbuchwerte. Der entry.ValueWert könnte eine weitere [Unter-] Sammlung sein.
ChrisW
6
@ChrisW: Nun, das verlangt, dass jeder Wert geklont wird - es liegt an der Clone()Methode, ob er tief oder flach ist. Ich habe eine Notiz zu diesem Effekt hinzugefügt.
Jon Skeet
1
@SaeedGanji: Nun, wenn die Werte nicht geklont werden müssen, ist das "Verwenden der Konstruktorüberladung für Dictionary, das ein vorhandenes IDictionary verwendet" in Ordnung und bereits in meiner Antwort enthalten. Wenn die Werte noch geklont werden müssen, dann ist die Antwort , die Sie verknüpft haben nicht hilft überhaupt nicht .
Jon Skeet
1
@SaeedGanji: Das sollte in Ordnung sein, ja. (Wenn die Strukturen Verweise auf veränderbare Referenztypen enthalten, könnte dies natürlich immer noch ein Problem sein ... aber hoffentlich ist das nicht der Fall.)
Jon Skeet
1
@SaeedGanji: Das hängt davon ab, was sonst noch los ist. Wenn andere Threads nur aus dem Originalwörterbuch lesen , sollte es meiner Meinung nach in Ordnung sein. Wenn irgendetwas daran geändert wird, müssen Sie sowohl diesen Thread als auch den Klon-Thread sperren, um zu verhindern, dass sie gleichzeitig auftreten. Wenn Sie Thread-Sicherheit bei der Verwendung von Wörterbüchern wünschen, verwenden Sie ConcurrentDictionary.
Jon Skeet
210

(Hinweis: Obwohl die Klonversion möglicherweise nützlich ist, ist für eine einfache flache Kopie der Konstruktor, den ich im anderen Beitrag erwähne, eine bessere Option.)

Wie tief soll die Kopie sein und welche Version von .NET verwenden Sie? Ich vermute, dass ein LINQ-Aufruf von ToDictionary, bei dem sowohl die Schlüssel- als auch die Elementauswahl angegeben werden, der einfachste Weg ist, wenn Sie .NET 3.5 verwenden.

Wenn es Ihnen beispielsweise nichts ausmacht, dass der Wert ein flacher Klon ist:

var newDictionary = oldDictionary.ToDictionary(entry => entry.Key,
                                               entry => entry.Value);

Wenn Sie T bereits auf die Implementierung von ICloneable beschränkt haben:

var newDictionary = oldDictionary.ToDictionary(entry => entry.Key, 
                                               entry => (T) entry.Value.Clone());

(Diese sind ungetestet, sollten aber funktionieren.)

Jon Skeet
quelle
Danke für die Antwort Jon. Ich benutze tatsächlich v2.0 des Frameworks.
Mikeymo
Was ist in diesem Zusammenhang "entry => entry.Key, entry => entry.Value"? Wie werde ich Schlüssel und Wert hinzufügen. Es zeigt einen Fehler an meinem Ende
Pratik
2
@Pratik: Das sind Lambda-Ausdrücke - Teil von C # 3.
Jon Skeet
2
Standardmäßig kopiert das ToDictionary von LINQ den Vergleicher nicht. Sie haben in Ihrer anderen Antwort das Kopieren des Vergleichers erwähnt, aber ich denke, diese Version des Klonens sollte auch den Vergleicher bestehen.
user420667
86
Dictionary<string, int> dictionary = new Dictionary<string, int>();

Dictionary<string, int> copy = new Dictionary<string, int>(dictionary);
Herald Smit
quelle
5
Die Zeiger der Werte sind immer noch dieselben. Wenn Sie Änderungen an den kopierten Werten anwenden, werden die Änderungen auch im Wörterbuchobjekt wiedergegeben.
Fokko Driesprong
3
@ FokkoDriesprong nein, es wird nicht, es kopiert nur die keyValuePairs in neues Objekt
17
Dies funktioniert definitiv gut - es wird ein Klon sowohl des Schlüssels als auch des Werts erstellt. Dies funktioniert natürlich nur, wenn der Wert KEIN Referenztyp ist. Wenn der Wert ein Referenztyp ist, wird effektiv nur eine Kopie der Schlüssel als flache Kopie verwendet.
Contango
1
@Contango Also in diesem Fall, da String und Int KEIN Referenztyp sind, funktioniert es richtig?
MonsterMMORPG
3
@ UğurAldanmaz Sie vergessen, eine tatsächliche Änderung an einem referenzierten Objekt zu testen. Sie testen nur das Ersetzen von Wertzeigern in den geklonten Wörterbüchern, was offensichtlich funktioniert. Ihre Tests schlagen
Jens
10

Für .NET 2.0 können Sie eine Klasse implementieren, die von Dictionaryund implementiert ICloneable.

public class CloneableDictionary<TKey, TValue> : Dictionary<TKey, TValue> where TValue : ICloneable
{
    public IDictionary<TKey, TValue> Clone()
    {
        CloneableDictionary<TKey, TValue> clone = new CloneableDictionary<TKey, TValue>();

        foreach (KeyValuePair<TKey, TValue> pair in this)
        {
            clone.Add(pair.Key, (TValue)pair.Value.Clone());
        }

        return clone;
    }
}

Sie können das Wörterbuch dann einfach durch Aufrufen der CloneMethode klonen . Natürlich erfordert diese Implementierung, dass der Werttyp des Wörterbuchs implementiert wird ICloneable, aber ansonsten ist eine generische Implementierung überhaupt nicht praktikabel.

Kompilieren Sie dies
quelle
8

Das funktioniert gut für mich

 // assuming this fills the List
 List<Dictionary<string, string>> obj = this.getData(); 

 List<Dictionary<string, string>> objCopy = new List<Dictionary<string, string>>(obj);

Wie Tomer Wolberg in den Kommentaren beschreibt, funktioniert dies nicht, wenn der Werttyp eine veränderbare Klasse ist.

BonifatiusK
quelle
1
Das braucht dringend Upvotes! Wenn das ursprüngliche Wörterbuch jedoch schreibgeschützt ist, funktioniert dies weiterhin: var newDict = readonlyDict.ToDictionary (kvp => kvp.Key, kvp => kvp.Value)
Stephan Ryer
2
Das funktioniert nicht, wenn der Werttyp eine veränderbare Klasse ist
Tomer Wolberg
5

Sie können jederzeit die Serialisierung verwenden. Sie können das Objekt serialisieren und dann deserialisieren. Dadurch erhalten Sie eine ausführliche Kopie des Wörterbuchs und aller darin enthaltenen Elemente. Jetzt können Sie eine tiefe Kopie jedes Objekts erstellen, das als [serialisierbar] markiert ist, ohne einen speziellen Code zu schreiben.

Hier sind zwei Methoden, die die binäre Serialisierung verwenden. Wenn Sie diese Methoden verwenden, rufen Sie einfach auf

object deepcopy = FromBinary(ToBinary(yourDictionary));

public Byte[] ToBinary()
{
  MemoryStream ms = null;
  Byte[] byteArray = null;
  try
  {
    BinaryFormatter serializer = new BinaryFormatter();
    ms = new MemoryStream();
    serializer.Serialize(ms, this);
    byteArray = ms.ToArray();
  }
  catch (Exception unexpected)
  {
    Trace.Fail(unexpected.Message);
    throw;
  }
  finally
  {
    if (ms != null)
      ms.Close();
  }
  return byteArray;
}

public object FromBinary(Byte[] buffer)
{
  MemoryStream ms = null;
  object deserializedObject = null;

  try
  {
    BinaryFormatter serializer = new BinaryFormatter();
    ms = new MemoryStream();
    ms.Write(buffer, 0, buffer.Length);
    ms.Position = 0;
    deserializedObject = serializer.Deserialize(ms);
  }
  finally
  {
    if (ms != null)
      ms.Close();
  }
  return deserializedObject;
}
Shaun Bowe
quelle
5

Der beste Weg für mich ist folgender:

Dictionary<int, int> copy= new Dictionary<int, int>(yourListOrDictionary);
nikssa23
quelle
3
Kopiert dies nicht nur die Referenz und nicht die Werte, da Dictionary ein Referenztyp ist? Das heißt, wenn Sie die Werte in einem ändern, ändert sich der Wert in dem anderen?
Goku
3

Die binäre Serialisierungsmethode funktioniert einwandfrei, aber in meinen Tests hat sie sich als 10-mal langsamer erwiesen als eine Nicht-Serialisierungsimplementierung des Klons. Getestet amDictionary<string , List<double>>

loty
quelle
Sind Sie sicher, dass Sie eine vollständige, tiefe Kopie erstellt haben? Sowohl die Zeichenfolgen als auch die Listen müssen tief kopiert werden. Es gibt auch einige Fehler in der Serialisierungsversion, die dazu führen, dass sie langsam ist: In ToBinary()der Serialize()Methode wird mit thisstatt aufgerufen yourDictionary. Dann wird FromBinary()das Byte [] zuerst manuell in den MemStream kopiert, kann aber einfach an seinen Konstruktor übergeben werden.
Jupiter
1

Das hat mir geholfen, als ich versucht habe, ein Wörterbuch <string, string> tief zu kopieren

Dictionary<string, string> dict2 = new Dictionary<string, string>(dict);

Viel Glück

Peter Feldman
quelle
Funktioniert gut für .NET 4.6.1. Dies sollte die aktualisierte Antwort sein.
Tallal Kazmi
0

Versuchen Sie dies, wenn Schlüssel / Werte ICloneable sind:

    public static Dictionary<K,V> CloneDictionary<K,V>(Dictionary<K,V> dict) where K : ICloneable where V : ICloneable
    {
        Dictionary<K, V> newDict = null;

        if (dict != null)
        {
            // If the key and value are value types, just use copy constructor.
            if (((typeof(K).IsValueType || typeof(K) == typeof(string)) &&
                 (typeof(V).IsValueType) || typeof(V) == typeof(string)))
            {
                newDict = new Dictionary<K, V>(dict);
            }
            else // prepare to clone key or value or both
            {
                newDict = new Dictionary<K, V>();

                foreach (KeyValuePair<K, V> kvp in dict)
                {
                    K key;
                    if (typeof(K).IsValueType || typeof(K) == typeof(string))
                    {
                        key = kvp.Key;
                    }
                    else
                    {
                        key = (K)kvp.Key.Clone();
                    }
                    V value;
                    if (typeof(V).IsValueType || typeof(V) == typeof(string))
                    {
                        value = kvp.Value;
                    }
                    else
                    {
                        value = (V)kvp.Value.Clone();
                    }

                    newDict[key] = value;
                }
            }
        }

        return newDict;
    }
Arvind
quelle
0

Als ich auf einen alten Beitrag antwortete, fand ich es nützlich, ihn wie folgt zu verpacken:

using System;
using System.Collections.Generic;

public class DeepCopy
{
  public static Dictionary<T1, T2> CloneKeys<T1, T2>(Dictionary<T1, T2> dict)
    where T1 : ICloneable
  {
    if (dict == null)
      return null;
    Dictionary<T1, T2> ret = new Dictionary<T1, T2>();
    foreach (var e in dict)
      ret[(T1)e.Key.Clone()] = e.Value;
    return ret;
  }

  public static Dictionary<T1, T2> CloneValues<T1, T2>(Dictionary<T1, T2> dict)
    where T2 : ICloneable
  {
    if (dict == null)
      return null;
    Dictionary<T1, T2> ret = new Dictionary<T1, T2>();
    foreach (var e in dict)
      ret[e.Key] = (T2)(e.Value.Clone());
    return ret;
  }

  public static Dictionary<T1, T2> Clone<T1, T2>(Dictionary<T1, T2> dict)
    where T1 : ICloneable
    where T2 : ICloneable
  {
    if (dict == null)
      return null;
    Dictionary<T1, T2> ret = new Dictionary<T1, T2>();
    foreach (var e in dict)
      ret[(T1)e.Key.Clone()] = (T2)(e.Value.Clone());
    return ret;
  }
}
Decaf Sux
quelle