Jaki jest najlepszy sposób klonowania / głębokiego kopiowania ogólnego słownika .NET <string, T>?


211

Mam ogólny słownik Dictionary<string, T>, który chciałbym zasadniczo zrobić Clone () z ... dowolnych sugestii.

Odpowiedzi:


184

W porządku .NET 2.0 odpowiada:

Jeśli nie musisz klonować wartości, możesz użyć przeciążenia konstruktora do Dictionary, który pobiera istniejące IDictionary. (Możesz również porównać moduł porównujący jako istniejący moduł porównujący słownika.)

Jeśli nie trzeba sklonować wartości, można użyć coś takiego:

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()Oczywiście zależy to również od bycia odpowiednio głębokim klonem.


Myślę jednak, że robi to tylko płytką kopię wartości słownika. entry.ValueWartość może być jeszcze inny [sub] kolekcja.
ChrisW

6
@ChrisW: Cóż, to wymaga sklonowania każdej wartości - zależy od Clone()metody, czy jest głęboka czy płytka. Dodałem notatkę do tego efektu.
Jon Skeet

1
@SaeedGanji: Cóż, jeśli wartości nie muszą być klonowane, „użycie przeciążenia konstruktora do słownika, który przyjmuje istniejące IDictionary” jest w porządku, i już w mojej odpowiedzi. Jeśli wartości nie muszą być klonowane, to odpowiedź już związana nie pomaga w ogóle.
Jon Skeet

1
@SaeedGanji: Tak, powinno być dobrze. (Oczywiście, jeśli struktury zawierają odniesienia do zmiennych typów odniesienia, może to nadal stanowić problem ... ale mam nadzieję, że tak nie jest.)
Jon Skeet

1
@SaeedGanji: To zależy od tego, co się dzieje. Jeśli inne wątki czytają tylko z oryginalnego słownika, uważam, że powinno być dobrze. Jeśli coś go modyfikuje, musisz zablokować zarówno ten wątek, jak i wątek klonujący, aby uniknąć ich wystąpienia w tym samym czasie. Jeśli chcesz korzystać z bezpieczeństwa wątków podczas korzystania ze słowników, użyj ConcurrentDictionary.
Jon Skeet

210

(Uwaga: chociaż wersja klonowania jest potencjalnie przydatna, dla zwykłego płytkiego kopiowania konstruktor, o którym wspomniałem w innym poście, jest lepszym rozwiązaniem).

Jak głęboko chcesz mieć kopię i jakiej wersji .NET używasz? Podejrzewam, że wywołanie LINQ do ToDictionary, określające zarówno klucz, jak i selektor elementów, będzie najłatwiejszym sposobem, jeśli używasz .NET 3.5.

Na przykład, jeśli nie przeszkadza ci, że wartość jest płytkim klonem:

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

Jeśli już ograniczyłeś T do implementacji ICloneable:

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

(Te są nieprzetestowane, ale powinny działać.)


Dzięki za odpowiedź Jon. Właściwie używam v2.0 frameworka.
mikeymo

Co to jest „entry => entry.Key, entry => entry.Value” w tym kontekście. Jak dodam klucz i wartość. To pokazuje błąd na moim końcu
Pratik

2
@Pratik: Są wyrażeniami lambda - część C # 3.
Jon Skeet

2
Domyślnie ToDictionary LINQ nie kopiuje urządzenia porównującego. Wspomniałeś o skopiowaniu porównania w drugiej odpowiedzi, ale myślę, że ta wersja klonowania powinna również przekazać moduł porównujący.
user420667,

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

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

5
Wskaźniki wartości są nadal takie same, jeśli zastosujesz zmiany wartości w kopii, zmiany zostaną również odzwierciedlone w obiekcie słownika.
Fokko Driesprong

3
@FokkoDriesprong nie, nie będzie, po prostu kopiuje keyValuePairs w nowym obiekcie

17
To zdecydowanie działa dobrze - tworzy klon zarówno klucza, jak i wartości. Oczywiście działa to tylko wtedy, gdy wartość NIE jest typem referencyjnym, jeśli wartość jest typem referencyjnym, wówczas faktycznie pobiera tylko kopię kluczy jako płytką kopię.
Contango

1
@Contango, więc w tym przypadku, ponieważ string i int NIE są typem referencyjnym, to zadziała poprawnie?
MonsterMMORPG,

3
@ UğurAldanmaz zapomniałeś przetestować rzeczywistą zmianę w obiekcie odniesienia, testujesz tylko zamianę wskaźników wartości w klonowanych słownikach, co oczywiście działa, ale twoje testy zakończą się niepowodzeniem, jeśli tylko zmienisz właściwości obiektów testowych, na przykład: dotnetfiddle.net / xmPPKr
Jens

10

W przypadku .NET 2.0 można zaimplementować klasę, która dziedziczy Dictionaryi implementuje 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;
    }
}

Następnie można sklonować słownik, po prostu wywołując Clonemetodę. Oczywiście ta implementacja wymaga implementacji typu wartości słownika ICloneable, ale w przeciwnym razie ogólna implementacja nie jest w ogóle praktyczna.


8

To działa dobrze dla mnie

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

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

Jak Tomer Wolberg opisuje w komentarzach, nie działa to, jeśli typem wartości jest zmienna klasa.


1
To poważnie potrzebuje aprobaty! Jeśli oryginalny słownik jest tylko do odczytu, to nadal będzie działać: var newDict = readonlyDict.ToDictionary (kvp => kvp.Key, kvp => kvp.Value)
Stephan Ryer

2
To nie działa, jeśli typem wartości jest zmienna klasa
Tomer Wolberg

5

Zawsze możesz użyć serializacji. Możesz serializować obiekt, a następnie deserializować go. To da ci głęboką kopię Słownika i wszystkich jego elementów. Teraz możesz utworzyć głęboką kopię dowolnego obiektu oznaczonego jako [Serializable] bez pisania specjalnego kodu.

Oto dwie metody, które wykorzystają Serializacja binarna. Jeśli używasz tych metod, po prostu dzwonisz

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

Najlepszy sposób dla mnie to:

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

3
czy to nie jest po prostu kopiowanie odwołania, a nie wartości, ponieważ słownik jest typem odwołania? oznacza to, że jeśli zmienisz wartości w jednym, zmieni to wartość w drugim?
Goku

3

Metoda szeregowania binarnego działa dobrze, ale w moich testach okazała się 10 razy wolniejsza niż implementacja klonowania bez serializacji. Testowałem toDictionary<string , List<double>>


Czy na pewno zrobiłeś pełną, głęboką kopię? Zarówno ciągi, jak i listy muszą zostać głęboko skopiowane. Istnieje również kilka błędów w wersji serializacji powodując powolne: w ToBinary()tej Serialize()metodzie jest wywołana thiszamiast yourDictionary. Następnie w FromBinary()bajcie [] jest najpierw kopiowany ręcznie do MemStream, ale można go po prostu dostarczyć do jego konstruktora.
Jowisz

1

To mi pomogło, gdy próbowałem głęboko skopiować słownik <ciąg, ciąg>

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

Powodzenia


Działa dobrze dla .NET 4.6.1. To powinna być zaktualizowana odpowiedź.
Tallal Kazmi

0

Spróbuj tego, jeśli klucz / wartości można 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

Odpowiadając na stary post, uznałem jednak, że warto go zawinąć w następujący sposób:

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;
  }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.