¿Cuál es la mejor manera de clonar / copiar en profundidad un Diccionario genérico .NET <string, T>?


211

Tengo un diccionario genérico Dictionary<string, T>que me gustaría hacer esencialmente un Clon () de ... cualquier sugerencia.

Respuestas:


185

De acuerdo, el .NET 2.0 responde:

Si no necesita clonar los valores, puede usar la sobrecarga del constructor en Dictionary que toma un IDictionary existente. (También puede especificar el comparador como el comparador del diccionario existente).

Si usted no necesita clonar los valores, puede usar algo como esto:

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

Eso se basa en TValue.Clone()ser un clon adecuadamente profundo también, por supuesto.


Sin embargo, creo que eso solo es una copia superficial de los valores del diccionario. El entry.Valuevalor podría ser otra [sub] colección.
ChrisW

66
@ChrisW: Bueno, eso es pedir que se clone cada valor, depende del Clone()método si es profundo o poco profundo. He añadido una nota a tal efecto.
Jon Skeet

1
@SaeedGanji: Bueno, si los valores no necesitan ser clonados, "usar la sobrecarga del constructor al Diccionario que toma un IDictionary existente" está bien, y ya está en mi respuesta. Si los valores no necesitan ser clonado, entonces la respuesta que ha enlazado a no ayuda en absoluto.
Jon Skeet

1
@SaeedGanji: Eso debería estar bien, sí. (Por supuesto, si las estructuras contienen referencias a tipos de referencia mutables, aún podría ser un problema ... pero espero que ese no sea el caso)
Jon Skeet

1
@SaeedGanji: Eso depende de qué más esté sucediendo. Si otros hilos solo leen del diccionario original, entonces creo que debería estar bien. Si algo lo modifica, necesitará bloquear tanto ese hilo como el hilo de clonación, para evitar que sucedan al mismo tiempo. Si desea seguridad de subprocesos al usar diccionarios, use ConcurrentDictionary.
Jon Skeet

210

(Nota: aunque la versión de clonación es potencialmente útil, para una copia superficial simple, el constructor que menciono en la otra publicación es una mejor opción).

¿Qué tan profunda desea que sea la copia y qué versión de .NET está usando? Sospecho que una llamada LINQ a ToDictionary, que especifica tanto la clave como el selector de elementos, será la forma más fácil de hacerlo si está utilizando .NET 3.5.

Por ejemplo, si no le importa que el valor sea un clon superficial:

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

Si ya ha restringido a T para implementar ICloneable:

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

(Esos no han sido probados, pero deberían funcionar).


Gracias por la respuesta Jon. En realidad estoy usando v2.0 del marco.
mikeymo

¿Qué es "entry => entry.Key, entry => entry.Value" en este contexto? ¿Cómo agregaré clave y valor?
Muestra

2
@Pratik: Son expresiones lambda - parte de C # 3.
Jon Skeet

2
Por defecto, ToDictionary de LINQ no copia el comparador. Usted mencionó copiar el comparador en su otra respuesta, pero creo que esta versión de clonación también debería pasar al comparador.
user420667

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

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

55
Los punteros de los valores siguen siendo los mismos, si aplica cambios a los valores en copia, los cambios también se reflejarán en el objeto del diccionario.
Fokko Driesprong

44
@FokkoDriesprong no, no, simplemente copia los keyValuePairs en un nuevo objeto

17
Esto definitivamente funciona bien: crea un clon de la clave y el valor. Por supuesto, esto solo funciona si el valor NO es un tipo de referencia, si el valor es un tipo de referencia, entonces solo toma una copia de las claves como una copia superficial.
Contango

1
@Contango, así que en este caso ya que string e int NO son un tipo de referencia, ¿funcionará?
MonsterMMORPG

3
@ UğurAldanmaz se olvida de probar un cambio real en un objeto referenciado, solo prueba el reemplazo de punteros de valor en los diccionarios clonados, lo que obviamente funciona, pero sus pruebas fallarán si solo cambia las propiedades de sus objetos de prueba, así: dotnetfiddle.net / xmPPKr
Jens,

10

Para .NET 2.0, podría implementar una clase que herede Dictionarye implemente 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;
    }
}

Luego puede clonar el diccionario simplemente llamando al Clonemétodo. Por supuesto, esta implementación requiere que se implemente el tipo de valor del diccionario ICloneable, pero de lo contrario, una implementación genérica no es práctica en absoluto.


8

Esto funciona bien para mi

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

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

Como Tomer Wolberg describe en los comentarios, esto no funciona si el tipo de valor es una clase mutable.


1
¡Esto realmente necesita votos a favor! Sin embargo, si el diccionario original es de solo lectura, seguirá funcionando: var newDict = readonlyDict.ToDictionary (kvp => kvp.Key, kvp => kvp.Value)
Stephan Ryer

2
Eso no funciona si el tipo de valor es una clase mutable
Tomer Wolberg

5

Siempre puedes usar la serialización. Puede serializar el objeto y luego deserializarlo. Eso le dará una copia profunda del Diccionario y todos los elementos que contiene. Ahora puede crear una copia profunda de cualquier objeto que esté marcado como [Serializable] sin escribir ningún código especial.

Aquí hay dos métodos que utilizarán la serialización binaria. Si usa estos métodos, simplemente llame

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

La mejor manera para mí es esta:

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

3
¿no es esto simplemente copiar la referencia y no los valores ya que Dictionary es un tipo de referencia? eso significa que si cambia los valores en uno, ¿cambiará el valor en el otro?
Goku

3

El método de serialización binaria funciona bien, pero en mis pruebas demostró ser 10 veces más lento que una implementación de clon sin serialización. Probado enDictionary<string , List<double>>


¿Estás seguro de que hiciste una copia profunda completa? Tanto las cadenas como las listas deben copiarse en profundidad. También hay algunos errores en la versión de serialización que hacen que sea lenta: en ToBinary()el Serialize()método se llama con en thislugar de yourDictionary. Luego, en FromBinary()el byte [] se copia primero manualmente en MemStream, pero solo se puede suministrar a su constructor.
Júpiter

1

Eso es lo que me ayudó, cuando estaba tratando de copiar en profundidad un Diccionario <string, string>

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

Buena suerte


Funciona bien para .NET 4.6.1. Esta debería ser la respuesta actualizada.
Tallal Kazmi

0

Pruebe esto si la clave / valores son 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

Respondiendo en una publicación anterior, sin embargo, me pareció útil envolverlo de la siguiente manera:

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;
  }
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.