Bir .NET genel Sözlük <string, T> klonlamak / derin kopyalamak için en iyi yolu nedir?


211

Ben Dictionary<string, T>aslında herhangi bir öneri Clone () yapmak istiyorum genel bir sözlük var .

Yanıtlar:


184

Tamam, .NET 2.0 cevaplar:

Değerleri klonlamanıza gerek yoksa, varolan bir IDictionary alan Sözlük için aşırı yükleyici kullanabilirsiniz. (Karşılaştırıcıyı mevcut sözlüğün karşılaştırıcısı olarak da belirleyebilirsiniz.)

Eğer varsa do değerleri klonlamak gerekir, böyle bir şey kullanabilirsiniz:

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

Tabii ki TValue.Clone()bu da uygun bir şekilde derin bir klon olmaya dayanır .


Bence bu sadece sözlük değerlerinin sığ bir kopyasını yapıyor. entry.ValueDeğer bir başka [sub] Koleksiyonu olabilir.
ChrisW

6
@ChrisW: Bu, her bir değerin klonlanmasını istiyor - Clone()derin ya da sığ olmasının yöntemine bağlı. Bu etkiye bir not ekledim.
Jon Skeet

1
@SaeedGanji: Değerlerin klonlanması gerekmiyorsa, "varolan bir IDictionary alan Sözlük için yapıcı aşırı yükünü kullan" gayet iyi ve zaten cevabımda. Değerleri ise do klonlanmış gerekir, o zaman bağlantı verdiğiniz cevabı hiç değil bu moral.
Jon Skeet

1
@SaeedGanji: Bu iyi olmalı, evet. (Tabii ki, yapılar değişebilir referans türlerine referanslar içeriyorsa, bu hala bir sorun olabilir ... ama umarım durum böyle değildir.)
Jon Skeet

1
@SaeedGanji: Bu, başka neler olup bittiğine bağlı. Diğer konular sadece orijinal sözlükten okuyorsa , o zaman iyi olması gerektiğine inanıyorum. Bir şey değiştiriliyorsa, aynı anda gerçekleşmelerini önlemek için hem o ipliği hem de klonlama ipliğini kilitlemeniz gerekir. Sözlükleri kullanırken iş parçacığı güvenliği istiyorsanız şunu kullanın:ConcurrentDictionary .
Jon Skeet

210

(Not: klonlama sürümü potansiyel olarak yararlı olsa da, basit bir sığ kopya için diğer yazıda bahsettiğim yapıcı daha iyi bir seçenektir.)

Kopyanın ne kadar derin olmasını istiyorsunuz ve hangi .NET sürümünü kullanıyorsunuz? ToDictionary için LINQ çağrısı, hem anahtar hem de öğe seçici belirterek, .NET 3.5 kullanıyorsanız gitmek için en kolay yolu olacağını sanıyorum.

Örneğin, değerin sığ bir klon olduğunu düşünmezseniz:

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

T'yi ICloneable uygulamak için zaten kısıtladıysanız:

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

(Bunlar test edilmemiştir, ancak çalışmalıdır.)


Cevabınız için teşekkürler Jon. Aslında çerçeve v2.0 kullanıyorum.
mikeymo

Bu bağlamda "entry => entry.Key, entry => entry.Value" nedir. Anahtar ve değeri nasıl ekleyeceğim. Sonunda bir hata gösteriyor
Pratik

2
@Pratik: Lamda ifadeleri - C # 3'ün bir parçası
Jon Skeet

2
Varsayılan olarak LINQ'nun ToDictionary karşılaştırıcısını kopyalamaz. Karşılaştırıcıyı diğer cevabınızda kopyaladığınızdan bahsettiniz, ancak klonlamanın bu versiyonunun da karşılaştırıcıyı geçmesi gerektiğini düşünüyorum.
user420667

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

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

5
Değerlerin işaretçileri hala aynıdır; kopyadaki değerlere değişiklikler uygularsanız, değişiklikler sözlük nesnesine de yansıtılır.
Fokko Driesprong

4
@FokkoDriesprong hayır alışkanlık, sadece keyValuePairs'i yeni nesnede kopyalar

17
Bu kesinlikle iyi çalışıyor - hem anahtarın hem de değerin bir klonunu yaratıyor. Tabii ki, bu sadece değer bir referans türü DEĞİLDİR, eğer değer bir referans türü ise, o zaman etkili bir şekilde sadece bir kopyasını sığ bir kopyası olarak alır.
Contango

1
@Contango bu nedenle string ve int bir referans türü DEĞİLDİR çünkü doğru çalışır?
MonsterMMORPG

3
@ UğurAldanmaz, referans verilen bir nesnede gerçek bir değişikliği test etmeyi unutursanız, yalnızca klonlanmış sözlüklerdeki değer işaretlerinin değiştirilmesini test edersiniz, ancak açıkça çalışır, ancak test nesnelerinizdeki özellikleri değiştirirseniz testleriniz başarısız olur: dotnetfiddle.net
Jens

10

.NET 2.0 için miras alan Dictionaryve uygulayan bir sınıf uygulayabilirsiniz 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;
    }
}

Daha sonra, sadece Cloneyöntemi çağırarak sözlüğü klonlayabilirsiniz . Elbette bu uygulama, sözlüğün değer türünün uygulanmasını gerektirir ICloneable, ancak aksi halde genel bir uygulama hiç pratik değildir.


8

Bu benim için iyi çalışıyor

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

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

Tomer Wolberg'in yorumlarda açıkladığı gibi, değer türü değiştirilebilir bir sınıfsa bu çalışmaz.


1
Bu ciddi upvotes gerekiyor! Ancak orijinal sözlük salt okunursa, bu yine de işe yarar: var newDict = readonlyDict.ToDictionary (kvp => kvp.Key, kvp => kvp.Value)
Stephan Ryer

2
Değer türü değiştirilebilir bir sınıfsa bu işe yaramaz
Tomer Wolberg

5

Serileştirmeyi her zaman kullanabilirsiniz. Nesneyi serileştirebilir ve ardından serisini kaldırabilirsiniz. Bu size Sözlüğün ve içindeki tüm öğelerin derin bir kopyasını verecektir. Artık [Serializable] olarak işaretlenmiş herhangi bir nesnenin özel bir kod yazmadan derin bir kopyasını oluşturabilirsiniz.

İşte İkili Serileştirmeyi kullanacak iki yöntem. Bu yöntemleri kullanırsanız,

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

Benim için en iyi yol şudur:

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

3
Bu sadece bir referans türü olduğu için sadece referansı değil, değerleri kopyalamak değil mi? birindeki değerleri değiştirirseniz, diğerindeki değeri değiştirir?
Goku

3

İkili Serileştirme yöntemi iyi çalışıyor ancak testlerimde klonun serileştirilmemiş bir uygulamasından 10 kat daha yavaş olduğu gösterildi. Üzerinde test edildiDictionary<string , List<double>>


Derinlemesine bir kopya yaptığınızdan emin misiniz? Hem dizelerin hem de Listelerin derin kopyalanması gerekir. Yavaş olmasına neden seri sürümünde bazı hatalar da vardır: in yöntemi ile çağrılır yerine . Sonra baytta [] ilk olarak MemStream'e manuel olarak kopyalanır, ancak sadece yapıcısına tedarik edilebilir. ToBinary()Serialize()thisyourDictionaryFromBinary()
Jüpiter

1

Bir Sözlük <string, string> derin kopyalamaya çalışırken bana yardımcı oldu

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

İyi şanslar


.NET 4.6.1 için iyi çalışır. Bu güncellenmiş cevap olmalıdır.
Tallal Kazmi

0

Anahtar / değerler ICloneable ise bunu deneyin:

    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

Eski yazı üzerine cevap ancak ben aşağıdaki gibi sarmak için yararlı buldum:

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;
  }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.