Qual é a melhor maneira de clonar / copiar em profundidade um dicionário genérico do .NET <string, T>?

211

Eu tenho um dicionário genérico Dictionary<string, T>que eu gostaria de essencialmente fazer um Clone () de ..qualquer sugestão.

mikeymo
fonte

Respostas:

185

Ok, o .NET 2.0 responde:

Se você não precisar clonar os valores, poderá usar a sobrecarga do construtor no Dictionary, que utiliza um IDictionary existente. (Você também pode especificar o comparador como o comparador do dicionário existente.)

Se você não precisa de clonar os valores, você pode usar algo como isto:

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

Isso depende de TValue.Clone()ser um clone adequadamente profundo, é claro.

Jon Skeet
fonte
Eu acho que isso está apenas fazendo uma cópia superficial dos valores do dicionário. O entry.Valuevalor pode ser mais uma [sub] coleção.
ChrisW
6
@ ChrisW: Bem, isso está pedindo que cada valor seja clonado - depende do Clone()método, seja profundo ou superficial. Eu adicionei uma nota para esse efeito.
precisa saber é o seguinte
1
@SaeedGanji: Bem, se os valores não precisarem ser clonados, o "use a sobrecarga de construtor no Dictionary, que leva um IDictionary existente" está bom, e já está na minha resposta. Se os valores não precisam ser clonado, em seguida, a resposta que você tiver vinculado a não ajuda em tudo.
Jon Skeet
1
@SaeedGanji: Tudo bem, sim. (Claro, se as estruturas conter referências a tipos de referência mutáveis, que pode ainda ser um problema ... mas espero que isso não é o caso.)
Jon Skeet
1
@SaeedGanji: Isso depende do que mais está acontecendo. Se outros tópicos estiverem apenas lendo do dicionário original, acredito que tudo esteja bem. Se alguma coisa estiver modificando-o, você precisará bloquear esse thread e o thread de clonagem, para evitar que eles aconteçam ao mesmo tempo. Se você deseja segurança de thread ao usar dicionários, use ConcurrentDictionary.
Jon Skeet
210

(Nota: embora a versão de clonagem seja potencialmente útil, para uma cópia superficial simples, o construtor que mencionei no outro post é uma opção melhor.)

Qual a profundidade da cópia e qual versão do .NET você está usando? Suspeito que uma chamada LINQ para o ToDictionary, especificando o seletor de chave e elemento, seja o caminho mais fácil, se você estiver usando o .NET 3.5.

Por exemplo, se você não se importa com o valor de ser um clone superficial:

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

Se você já restringiu o T a implementar o ICloneable:

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

(Eles não foram testados, mas devem funcionar.)

Jon Skeet
fonte
Obrigado pela resposta Jon. Na verdade, estou usando a v2.0 da estrutura.
Mikeymo 26/09/08
O que é "entry => entry.Key, entry => entry.Value" neste contexto. Como vou adicionar chave e valor. Ele mostra um erro no meu final
Pratik 30/07/2013
2
@Pratik: Eles são expressões lambda - parte C # 3.
Jon Skeet
2
Por padrão, o ToDictionary do LINQ não copia o comparador. Você mencionou copiar o comparador em sua outra resposta, mas acho que essa versão da clonagem também deve passar no comparador.
user420667
86
Dictionary<string, int> dictionary = new Dictionary<string, int>();

Dictionary<string, int> copy = new Dictionary<string, int>(dictionary);
Herald Smit
fonte
5
Os ponteiros dos valores ainda são os mesmos. Se você aplicar alterações aos valores em cópia, as alterações também serão refletidas no objeto do dicionário.
Fokko Driesprong
4
@FokkoDriesprong não, não, ele apenas copia o keyValuePairs no novo objeto
17
Definitivamente, isso funciona bem - cria um clone da chave e do valor. Obviamente, isso só funciona se o valor NÃO for um tipo de referência, se o valor for um tipo de referência e, efetivamente, apenas uma cópia das chaves será copiada como uma cópia superficial.
Contango
1
@ Contango, neste caso, uma vez que string e int NÃO são um tipo de referência, ele funcionará certo?
MonsterMMORPG
3
@ UğurAldanmaz você esquece de testar uma mudança real em um objeto referenciado, você só testa a substituição de indicadores de valor nos dicionários clonados que obviamente funcionam, mas seus testes falharão se você alterar as propriedades dos objetos de teste, como: dotnetfiddle.net / xmPPKr
Jens
10

Para o .NET 2.0, você pode implementar uma classe que herda 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;
    }
}

Você pode clonar o dicionário simplesmente chamando o Clonemétodo É claro que essa implementação exige que o tipo de valor do dicionário implemente ICloneable, mas, caso contrário, uma implementação genérica não é prática.

Compilar isto
fonte
8

Este trabalho é bom para mim

 // 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 descreve nos comentários, isso não funciona se o tipo de valor for uma classe mutável.

BonifatiusK
fonte
1
Isso precisa seriamente de votos! Se o dicionário original está somente leitura, no entanto, isso ainda vai funcionar: var newDict = readonlyDict.ToDictionary (kvp => kvp.Key, kvp => kvp.Value)
Stephan Ryer
2
Isso não funciona se o tipo de valor é uma classe mutável
Tomer Wolberg
5

Você sempre pode usar serialização. Você pode serializar o objeto e desserializá-lo. Isso fornecerá uma cópia detalhada do dicionário e de todos os itens dentro dele. Agora você pode criar uma cópia profunda de qualquer objeto que esteja marcado como [Serializable] sem escrever nenhum código especial.

Aqui estão dois métodos que usarão serialização binária. Se você usar esses métodos, basta chamar

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
fonte
5

A melhor maneira para mim é esta:

Dictionary<int, int> copy= new Dictionary<int, int>(yourListOrDictionary);
nikssa23
fonte
3
isso não é apenas copiar a referência e não os valores, já que Dictionary é um tipo de referência? isso significa que se você alterar os valores em um, ele mudará o valor no outro?
Goku
3

O método de serialização binária funciona bem, mas nos meus testes ele mostrou ser 10 vezes mais lento que uma implementação de clone sem serialização. Testado emDictionary<string , List<double>>

loty
fonte
Tem certeza de que fez uma cópia completa e profunda? As seqüências de caracteres e as listas precisam ser copiadas em profundidade. Existem também alguns bugs na versão de serialização fazendo com que ele a ser lenta: ToBinary()no Serialize()método é chamado com thisem vez de yourDictionary. Em seguida, no FromBinary()byte [] é copiado manualmente manualmente para o MemStream, mas pode ser fornecido apenas ao seu construtor.
Jupiter
1

Foi isso que me ajudou, quando eu estava tentando copiar profundamente um Dictionary <string, string>

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

Boa sorte

Peter Feldman
fonte
Funciona bem para o .NET 4.6.1. Essa deve ser a resposta atualizada.
Tallal Kazmi
0

Tente isso se os valores / chave forem 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;
    }
Arvind
fonte
0

Respondendo a post antigo, no entanto, achei útil envolvê-lo da seguinte maneira:

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
fonte