Quelle est la meilleure façon de cloner / copier en profondeur un dictionnaire générique .NET <chaîne, T>?

211

J'ai un dictionnaire générique Dictionary<string, T>que je voudrais essentiellement faire un clone () de .. toutes les suggestions.

mikeymo
la source

Réponses:

185

D'accord, le .NET 2.0 répond:

Si vous n'avez pas besoin de cloner les valeurs, vous pouvez utiliser la surcharge du constructeur dans Dictionary qui prend un IDictionary existant. (Vous pouvez également spécifier le comparateur comme comparateur du dictionnaire existant.)

Si vous ne devez cloner les valeurs, vous pouvez utiliser quelque chose comme ceci:

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

TValue.Clone()Bien entendu, cela repose également sur un clone suffisamment profond.

Jon Skeet
la source
Je pense que cela ne fait qu'une copie superficielle des valeurs du dictionnaire, cependant. La entry.Valuevaleur peut être encore une autre [sous] collection.
ChrisW
6
@ChrisW: Eh bien, c'est demander à chaque valeur d'être clonée - c'est à la Clone()méthode si elle est profonde ou peu profonde. J'ai ajouté une note à cet effet.
Jon Skeet
1
@SaeedGanji: Eh bien, si les valeurs n'ont pas besoin d'être clonées, "utiliser la surcharge du constructeur dans Dictionary qui prend un IDictionary existant" est très bien, et déjà dans ma réponse. Si les valeurs ne doivent être clonés, alors la réponse que vous avez lié à ne pas d' aide du tout.
Jon Skeet
1
@SaeedGanji: Ça devrait aller, oui. (Bien sûr, si les structures contiennent des références à des types de référence mutables, cela pourrait toujours être un problème ... mais j'espère que ce n'est pas le cas.)
Jon Skeet
1
@SaeedGanji: Cela dépend de ce qui se passe d'autre. Si d'autres fils ne lisent que dans le dictionnaire d'origine, alors je pense que ça devrait aller. Si quelque chose le modifie, vous devrez verrouiller à la fois ce thread et le thread de clonage, pour éviter qu'ils ne se produisent en même temps. Si vous souhaitez la sécurité des threads lorsque vous utilisez des dictionnaires, utilisez ConcurrentDictionary.
Jon Skeet
210

(Remarque: bien que la version de clonage soit potentiellement utile, pour une simple copie superficielle, le constructeur que je mentionne dans l'autre post est une meilleure option.)

Quelle profondeur souhaitez-vous que la copie soit et quelle version de .NET utilisez-vous? Je soupçonne qu'un appel LINQ à ToDictionary, spécifiant à la fois la clé et le sélecteur d'élément, sera le moyen le plus simple si vous utilisez .NET 3.5.

Par exemple, si cela ne vous dérange pas que la valeur soit un clone peu profond:

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

Si vous avez déjà contraint T à implémenter ICloneable:

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

(Ceux-ci ne sont pas testés, mais devraient fonctionner.)

Jon Skeet
la source
Merci pour la réponse Jon. J'utilise en fait la version 2.0 du framework.
mikeymo
Qu'est-ce que "entrée => entrée.Clé, entrée => entrée.Valeur" dans ce contexte. Comment ajouter la clé et la valeur. Il montre une erreur de ma part
Pratik
2
@Pratik: Ce sont des expressions lambda - partie de C # 3.
Jon Skeet
2
Par défaut, ToDictionary de LINQ ne copie pas le comparateur. Vous avez mentionné la copie du comparateur dans votre autre réponse, mais je pense que cette version du clonage devrait également passer le comparateur.
user420667
86
Dictionary<string, int> dictionary = new Dictionary<string, int>();

Dictionary<string, int> copy = new Dictionary<string, int>(dictionary);
Herald Smit
la source
5
Les pointeurs des valeurs sont toujours les mêmes, si vous appliquez des modifications aux valeurs dans la copie, les modifications seront également reflétées dans l'objet dictionnaire.
Fokko Driesprong
4
@FokkoDriesprong no it wont, il copie simplement les keyValuePairs dans le nouvel objet
17
Cela fonctionne certainement bien - cela crée un clone de la clé et de la valeur. Bien sûr, cela ne fonctionne que si la valeur n'est PAS un type de référence, si la valeur est un type de référence, elle ne prend en fait qu'une copie des clés comme une copie superficielle.
Contango
1
@Contango donc dans ce cas puisque string et int ne sont PAS un type de référence, cela fonctionnera-t-il correctement?
MonsterMMORPG
3
@ UğurAldanmaz vous oubliez de tester une modification réelle d'un objet référencé, vous testez uniquement le remplacement des pointeurs de valeur dans les dictionnaires clonés qui fonctionne évidemment, mais vos tests échoueront si vous modifiez simplement les propriétés de vos objets de test, comme ceci: dotnetfiddle.net / xmPPKr
Jens
10

Pour .NET 2.0, vous pouvez implémenter une classe qui hérite de Dictionaryet implémente 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;
    }
}

Vous pouvez ensuite cloner le dictionnaire simplement en appelant la Cloneméthode. Bien sûr, cette implémentation nécessite que le type de valeur du dictionnaire implémente ICloneable, mais sinon une implémentation générique n'est pas du tout pratique.

Compilez ceci
la source
8

Cela fonctionne bien pour moi

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

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

Comme Tomer Wolberg le décrit dans les commentaires, cela ne fonctionne pas si le type de valeur est une classe mutable.

BonifatiusK
la source
1
Cela nécessite sérieusement des votes positifs! Cependant, si le dictionnaire d'origine est en lecture seule, cela fonctionnera toujours: var newDict = readonlyDict.ToDictionary (kvp => kvp.Key, kvp => kvp.Value)
Stephan Ryer
2
Cela ne fonctionne pas si le type de valeur est une classe mutable
Tomer Wolberg
5

Vous pouvez toujours utiliser la sérialisation. Vous pouvez sérialiser l'objet puis le désérialiser. Cela vous donnera une copie complète du dictionnaire et de tous les éléments qu'il contient. Vous pouvez maintenant créer une copie complète de tout objet marqué comme [Sérialisable] sans écrire de code spécial.

Voici deux méthodes qui utiliseront la sérialisation binaire. Si vous utilisez ces méthodes, vous appelez simplement

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
la source
5

La meilleure façon pour moi est la suivante:

Dictionary<int, int> copy= new Dictionary<int, int>(yourListOrDictionary);
nikssa23
la source
3
N'est-ce pas simplement copier la référence et non les valeurs puisque Dictionary est un type de référence? cela signifie que si vous changez les valeurs dans l'un, cela changera la valeur dans l'autre?
Goku
3

La méthode de sérialisation binaire fonctionne bien, mais dans mes tests, elle s'est révélée 10 fois plus lente qu'une implémentation de non-sérialisation de clone. Testé surDictionary<string , List<double>>

loty
la source
Êtes-vous sûr d'avoir fait une copie complète et complète? Les chaînes et les listes doivent être copiées en profondeur. Il y a aussi quelques bugs dans la version de sérialisation qui la rendent lente: dans ToBinary()la Serialize()méthode est appelée avec thisau lieu de yourDictionary. Ensuite, dans FromBinary()l'octet [] est d'abord copié manuellement sur le MemStream mais il peut simplement être fourni à son constructeur.
Jupiter
1

C'est ce qui m'a aidé, lorsque j'essayais de copier en profondeur un dictionnaire <chaîne, chaîne>

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

Bonne chance

peter feldman
la source
Fonctionne bien pour .NET 4.6.1. Cela devrait être la réponse mise à jour.
Tallal Kazmi
0

Essayez ceci si les clés / valeurs sont IClonables:

    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
la source
0

En répondant à un ancien post, j'ai trouvé utile de le résumer comme suit:

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
la source