Qual è il modo migliore per clonare / copiare in profondità un dizionario generico .NET <stringa, T>?


211

Ho un dizionario generico Dictionary<string, T>che vorrei essenzialmente fare un clone () di ..ogni suggerimento.

Risposte:


185

Bene, .NET 2.0 risponde:

Se non è necessario clonare i valori, è possibile utilizzare il overload del costruttore su Dizionario che accetta un IDictionary esistente. (Puoi anche specificare il comparatore come comparatore del dizionario esistente.)

Se non c'è bisogno di clonare i valori, è possibile utilizzare qualcosa di simile a questo:

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

Ciò dipende TValue.Clone()ovviamente dall'essere un clone adeguatamente profondo.


Penso che stia facendo solo una copia superficiale dei valori del dizionario. Il entry.Valuevalore potrebbe essere ancora un'altra [sotto] raccolta.
ChrisW,

6
@ChrisW: Beh, questo richiede la clonazione di ogni valore: dipende dal Clone()metodo se è profondo o superficiale. Ho aggiunto una nota in tal senso.
Jon Skeet,

1
@SaeedGanji: Beh, se i valori non devono essere clonati, "usa il sovraccarico del costruttore nel dizionario che accetta un IDictionary esistente" va bene, e già nella mia risposta. Se i valori non hanno bisogno di essere clonato, quindi la risposta che hai collegato a non aiuta affatto.
Jon Skeet,

1
@SaeedGanji: dovrebbe andare bene, sì. (Naturalmente, se le strutture contengono riferimenti a tipi di riferimento mutabili, questo potrebbe ancora essere un problema ... ma speriamo che non sia così.)
Jon Skeet,

1
@SaeedGanji: dipende da cosa sta succedendo. Se altri thread leggono solo dal dizionario originale, allora credo che dovrebbe andare bene. Se qualcosa lo sta modificando, dovrai bloccare sia il thread che il thread di clonazione, per evitare che accadano contemporaneamente. Se si desidera la sicurezza dei thread durante l'utilizzo dei dizionari, utilizzare ConcurrentDictionary.
Jon Skeet,

210

(Nota: sebbene la versione della clonazione sia potenzialmente utile, per una semplice copia superficiale il costruttore che cito nell'altro post è un'opzione migliore.)

Quanto in profondità vuoi che sia la copia e quale versione di .NET stai usando? Sospetto che una chiamata LINQ a ToDictionary, specificando sia la chiave che il selettore di elementi, sarà il modo più semplice di procedere se si utilizza .NET 3.5.

Ad esempio, se non ti dispiace che il valore sia un clone superficiale:

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

Se hai già vincolato T per implementare ICloneable:

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

(Quelli non sono testati, ma dovrebbero funzionare.)


Grazie per la risposta Jon. In realtà sto usando la versione 2.0 del framework.
mikeymo,

Che cos'è "entry => entry.Key, entry => entry.Value" in questo contesto. Come aggiungerò chiave e valore. Mostra un errore alla mia fine
Pratik,

2
@Pratik: sono espressioni lambda - parte di C # 3.
Jon Skeet,

2
Per impostazione predefinita, LINQ ToDictionary non copia il comparatore. Hai menzionato la copia del comparatore nell'altra tua risposta, ma sto pensando che questa versione della clonazione dovrebbe passare anche al comparatore.
user420667,

86
Dictionary<string, int> dictionary = new Dictionary<string, int>();

Dictionary<string, int> copy = new Dictionary<string, int>(dictionary);

5
I puntatori dei valori sono sempre gli stessi, se si applicano modifiche ai valori in copia, le modifiche si rifletteranno anche nell'oggetto dizionario.
Fokko Driesprong,

4
@FokkoDriesprong no, non lo fa, copia semplicemente keyValuePairs in un nuovo oggetto

17
Funziona sicuramente bene: crea un clone sia della chiave che del valore. Ovviamente, questo funziona solo se il valore NON è un tipo di riferimento, se il valore è un tipo di riferimento, in effetti prende solo una copia delle chiavi come copia superficiale.
Contango,

1
@Contango quindi in questo caso poiché string e int NON sono un tipo di riferimento funzionerà bene?
MonsterMMORPG,

3
@ UğurAldanmaz ti dimentichi di testare una modifica effettiva a un oggetto referenziato, testerai solo la sostituzione dei puntatori di valore nei dizionari clonati che ovviamente funziona, ma i tuoi test falliranno se cambi solo le proprietà sugli oggetti di test, in questo modo: dotnetfiddle.net / xmPPKr
Jens,

10

Per .NET 2.0 è possibile implementare una classe che eredita Dictionarye implementa 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;
    }
}

È quindi possibile clonare il dizionario semplicemente chiamando il Clonemetodo. Naturalmente questa implementazione richiede che il tipo di valore del dizionario sia implementato ICloneable, ma per il resto un'implementazione generica non è affatto pratica.


8

Questo funziona bene per me

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

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

Come descritto da Tomer Wolberg nei commenti, questo non funziona se il tipo di valore è una classe mutabile.


1
Questo ha seriamente bisogno di voti! Se il dizionario originale è di sola lettura, tuttavia, funzionerà comunque: var newDict = readonlyDict.ToDictionary (kvp => kvp.Key, kvp => kvp.Value)
Stephan Ryer,

2
Ciò non funziona se il tipo di valore è una classe mutabile
Tomer Wolberg

5

Puoi sempre usare la serializzazione. È possibile serializzare l'oggetto quindi deserializzarlo. Questo ti darà una copia approfondita del Dizionario e di tutti gli elementi al suo interno. Ora puoi creare una copia profonda di qualsiasi oggetto contrassegnato come [serializzabile] senza scrivere alcun codice speciale.

Ecco due metodi che useranno la serializzazione binaria. Se usi questi metodi, chiami semplicemente

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

5

Il modo migliore per me è questo:

Dictionary<int, int> copy= new Dictionary<int, int>(yourListOrDictionary);

3
non si tratta solo di copiare il riferimento e non i valori poiché Dictionary è un tipo di riferimento? ciò significa che se cambi i valori in uno cambierà il valore nell'altro?
Goku,

3

Il metodo di serializzazione binaria funziona bene, ma nei miei test ha dimostrato di essere 10 volte più lento di un'implementazione non seriale di clone. Testato suDictionary<string , List<double>>


Sei sicuro di aver fatto una copia approfondita completa? Sia le stringhe che gli Elenchi devono essere copiati in profondità. Ci sono anche alcuni bug nella versione serializzazione inducendolo a essere lento: nel ToBinary()il Serialize()metodo viene chiamato con thisal posto di yourDictionary. Quindi nel FromBinary()byte [] viene prima copiato manualmente in MemStream ma può essere semplicemente fornito al suo costruttore.
Giove,

1

Questo è ciò che mi ha aiutato, quando stavo cercando di copiare in profondità un dizionario <stringa, stringa>

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

In bocca al lupo


Funziona bene con .NET 4.6.1. Questa dovrebbe essere la risposta aggiornata.
Tallal Kazmi,

0

Prova questo se chiave / valori sono ICloneable:

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

0

Rispondendo ai vecchi post però ho trovato utile avvolgerlo come segue:

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;
  }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.