Apa cara terbaik untuk mengkloning / menyalin dalam .NET generic Dictionary <string, T>?

211

Saya punya kamus umum Dictionary<string, T>yang ingin saya buat pada dasarnya Clone () dari saran .any.

mikeymo
sumber

Jawaban:

185

Oke, .NET 2.0 menjawab:

Jika Anda tidak perlu mengkloning nilai-nilai, Anda dapat menggunakan konstruktor berlebihan ke Kamus yang mengambil IDictionary yang ada. (Anda juga dapat menentukan pembanding sebagai pembanding kamus yang ada.)

Jika Anda tidak perlu untuk mengkloning nilai-nilai, Anda dapat menggunakan sesuatu seperti ini:

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

Itu bergantung pada TValue.Clone()menjadi klon yang dalam juga, tentu saja.

Jon Skeet
sumber
Saya pikir itu hanya melakukan salinan nilai kamus yang dangkal. The entry.Valuenilai mungkin lagi [sub] koleksi.
ChrisW
6
@ ChrisW: Yah itu meminta setiap nilai untuk dikloning - terserah Clone()metode apakah itu dalam atau dangkal. Saya telah menambahkan catatan untuk efek itu.
Jon Skeet
1
@ SaeedGanji: Baik jika nilainya tidak perlu dikloning, "gunakan overload konstruktor ke Kamus yang menggunakan IDictionary yang ada" baik-baik saja, dan sudah ada dalam jawaban saya. Jika nilai-nilai yang perlu kloning, maka jawaban Anda terkait dengan tidak membantu sama sekali.
Jon Skeet
1
@ SaeedGanji: Itu seharusnya baik-baik saja, ya. (Tentu saja, jika struct berisi referensi ke tipe referensi yang bisa berubah, itu masih bisa menjadi masalah ... tapi mudah-mudahan bukan itu masalahnya.)
Jon Skeet
1
@ SaeedGanji: Itu tergantung pada apa yang sedang terjadi. Jika utas lain hanya membaca dari kamus asli, maka saya percaya itu akan baik-baik saja. Jika ada yang memodifikasinya, Anda harus mengunci utas itu dan utas kloning, agar tidak terjadi secara bersamaan. Jika Anda ingin keamanan utas saat menggunakan kamus, gunakan ConcurrentDictionary.
Jon Skeet
210

(Catatan: meskipun versi kloning berpotensi berguna, untuk menyalin dangkal konstruktor yang saya sebutkan di posting lain adalah pilihan yang lebih baik.)

Seberapa dalam Anda menginginkan salinannya, dan versi .NET apa yang Anda gunakan? Saya menduga bahwa panggilan LINQ ke ToDictionary, menentukan kunci dan elemen pemilih, akan menjadi cara termudah untuk pergi jika Anda menggunakan .NET 3.5.

Misalnya, jika Anda tidak keberatan nilainya menjadi klon dangkal:

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

Jika Anda sudah membatasi T untuk mengimplementasikan ICloneable:

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

(Itu belum diuji, tetapi harus bekerja.)

Jon Skeet
sumber
Terima kasih atas jawabannya, Jon. Saya sebenarnya menggunakan v2.0 dari framework.
mikeymo
Apa itu "entry => entry.Key, entry => entry.Value" dalam konteks ini. Bagaimana saya akan menambahkan kunci dan nilai. Itu menunjukkan kesalahan di akhir saya
Pratik
2
@Pratik: Itu adalah ekspresi lambda - bagian dari C # 3.
Jon Skeet
2
Secara default ToDictionary LINQ tidak menyalin pembanding. Anda menyebutkan menyalin pembanding di jawaban Anda yang lain, tapi saya pikir versi kloning ini juga harus melewati pembanding.
user420667
86
Dictionary<string, int> dictionary = new Dictionary<string, int>();

Dictionary<string, int> copy = new Dictionary<string, int>(dictionary);
Herald Smit
sumber
5
Pointer nilai masih sama, jika Anda menerapkan perubahan pada nilai-nilai dalam salinan, perubahan juga akan tercermin dalam objek kamus.
Fokko Driesprong
4
@FokkoDriesprong no it wont, itu hanya menyalin keyValuePairs di objek baru
17
Ini benar-benar berfungsi dengan baik - itu menciptakan tiruan dari kunci dan nilai. Tentu saja, ini hanya berfungsi jika nilainya BUKAN tipe referensi, jika nilainya adalah tipe referensi maka secara efektif hanya mengambil salinan kunci sebagai salinan dangkal.
Contango
1
@ Contango jadi dalam hal ini karena string dan int BUKAN jenis referensi itu akan berfungsi dengan benar?
MonsterMMORPG
3
@ UğurAldanmaz Anda lupa menguji perubahan aktual ke objek yang direferensikan, Anda hanya menguji penggantian pointer nilai di kamus yang dikloning yang jelas berfungsi, tetapi pengujian Anda akan gagal jika Anda hanya mengubah properti pada objek pengujian Anda, seperti: dotnetfiddle.net / xmPPKr
Jens
10

Untuk .NET 2.0 Anda bisa mengimplementasikan kelas yang mewarisi dari Dictionarydan mengimplementasikan 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;
    }
}

Anda kemudian dapat mengkloning kamus hanya dengan memanggil Clonemetode. Tentu saja implementasi ini mensyaratkan bahwa jenis nilai dari kamus mengimplementasikan ICloneable, tetapi jika tidak, implementasi generik sama sekali tidak praktis.

Kompilasi ini
sumber
8

Ini berfungsi baik untuk saya

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

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

Seperti yang dijelaskan Tomer Wolberg dalam komentar, ini tidak berfungsi jika tipe nilai adalah kelas yang bisa berubah.

BonifatiusK
sumber
1
Ini benar-benar membutuhkan upvotes! Namun, jika kamus asli hanya dapat dibaca, ini masih akan berfungsi: var newDict = readonlyDict.ToDictionary (kvp => kvp.Key, kvp => kvp.Value)
Stephan Ryer
2
Itu tidak berfungsi jika tipe nilai adalah kelas yang bisa berubah
Tomer Wolberg
5

Anda selalu dapat menggunakan serialisasi. Anda bisa membuat cerita bersambung objek lalu deserialisasi. Itu akan memberi Anda salinan yang mendalam dari Kamus dan semua item di dalamnya. Sekarang Anda dapat membuat salinan dalam dari objek apa pun yang ditandai sebagai [Serializable] tanpa menulis kode khusus apa pun.

Berikut adalah dua metode yang akan menggunakan Serialisasi Binary. Jika Anda menggunakan metode ini, Anda cukup menelepon

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

Cara terbaik bagi saya adalah ini:

Dictionary<int, int> copy= new Dictionary<int, int>(yourListOrDictionary);
nikssa23
sumber
3
bukankah ini hanya menyalin referensi dan bukan nilainya karena Kamus adalah tipe referensi? itu berarti jika Anda mengubah nilai di satu itu akan mengubah nilai di yang lain?
Goku
3

Metode Serialisasi Binary berfungsi dengan baik tetapi dalam tes saya itu menunjukkan 10x lebih lambat daripada implementasi non-serialisasi klon. MengujinyaDictionary<string , List<double>>

banyak sekali
sumber
Apakah Anda yakin telah menyalin sepenuhnya? Baik string dan Daftar harus disalin dalam-dalam. Ada juga beberapa bug di versi serialisasi menyebabkan ia menjadi lambat: di ToBinary()dalam Serialize()metode ini disebut dengan thisbukan yourDictionary. Kemudian dalam FromBinary()byte [] disalin pertama kali secara manual ke MemStream tetapi hanya dapat diberikan ke konstruktornya.
Jupiter
1

Itulah yang membantu saya, ketika saya mencoba menyalin Kamus <string, string>

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

Semoga berhasil

Peter Feldman
sumber
Bekerja dengan baik untuk .NET 4.6.1. Ini harus menjadi jawaban yang diperbarui.
Tallal Kazmi
0

Coba ini jika kunci / nilai 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
sumber
0

Membalas posting lama namun saya merasa berguna untuk membungkusnya sebagai berikut:

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
sumber