Який найкращий спосіб клонувати / глибоко скопіювати загальний словник .NET <string, T>?


211

У мене є загальний словник, Dictionary<string, T>який я б хотів по суті зробити клоном () з багатьох пропозицій.

Відповіді:


184

Гаразд, .NET 2.0 відповідає:

Якщо вам не потрібно клонувати значення, ви можете використовувати перевантаження конструктора на словник, який приймає існуючий IDictionary. (Ви також можете вказати компаратор як порівняльник існуючого словника.)

Якщо вам дійсно потрібно клонувати значення, ви можете використовувати що - щось на зразок цього:

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(), звичайно, покладається на те, що це, відповідно, глибокий клон.


Я думаю, що це лише невелика копія значень словника. entry.ValueЗначення може бути ще один [суб] колекція.
ChrisW

6
@ChrisW: Ну, це прохання клонувати кожне значення - це Clone()залежно від методу, глибоке чи дрібне. Я додав до цього записку.
Джон Скіт

1
@SaeedGanji: Ну, якщо значення не потрібно клонувати, "добре використовувати перевантаження конструктора на словник, який приймає існуючий ID Dictionary", це добре, і вже в моїй відповіді. Якщо значення цього потрібно клонувати, то відповідь ви пов'язані не допомагає взагалі.
Джон Скіт

1
@SaeedGanji: Це повинно бути добре, так. (Звичайно, якщо структури містять посилання на змінні типи посилань, це все-таки може бути проблемою ... але, сподіваємось, це не так.)
Джон Скіт

1
@SaeedGanji: Це залежить від того, що ще відбувається. Якщо інші теми читаються лише з оригінального словника, то я вважаю, що це повинно бути добре. Якщо що-небудь модифікує це, вам потрібно буде зафіксувати і цю нитку, і нитку клонування, щоб уникнути їх одночасного. Якщо ви хочете забезпечити безпеку ниток під час використання словників, використовуйте ConcurrentDictionary.
Джон Скіт

210

(Примітка. Хоча версія для клонування потенційно корисна, для простої дрібної копії конструктор, який я згадую в іншій публікації, є кращим варіантом.)

Наскільки глибока ви хочете, щоб копія була, і яку версію .NET ви використовуєте? Я підозрюю, що виклик LINQ в ToDictionary із зазначенням і ключа, і вибору елемента буде найпростішим шляхом, якщо ви використовуєте .NET 3.5.

Наприклад, якщо ви не заперечуєте, щоб значення було дрібним клоном:

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

Якщо ви вже обмежили T для реалізації ICloneable:

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

(Вони неперевірені, але повинні працювати.)


Дякую за відповідь, Джон. Я фактично використовую v2.0 фреймворку.
mikeymo

Що таке "entry => entry.Key, entry => entry.Value" в цьому контексті. Як додати ключ та значення. Він показує помилку в моєму кінці
Пратік

2
@Pratik: Вони - лямбдаські вирази - частина С # 3.
Джон Скіт

2
За замовчуванням ToDictionary LINQ не копіює компаратор. Ви згадали про копіювання компаратора у своїй іншій відповіді, але я думаю, що ця версія клонування також повинна пройти порівняння.
user420667

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

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

5
Покажчики значень залишаються однаковими, якщо застосувати зміни до значень у копії, зміни також відобразяться у словниковому об’єкті.
Fokko Driesprong

3
@FokkoDriesprong ні, це не просто, він просто копіює keyValuePairs в новий об’єкт

17
Це, безумовно, працює добре - це створює клон і ключа, і значення. Звичайно, це працює лише в тому випадку, якщо значення НЕ є еталонним типом, якщо значення є еталонним типом, то воно фактично приймає лише копію ключів як її неглибоку копію.
Контанго

1
@Contango, тож у цьому випадку, оскільки string та int НЕ є еталонним типом, він буде працювати правильно?
MonsterMMORPG

3
@ UğurAldanmaz ви забудете перевірити фактичну зміну посилається на об'єкт, ви перевіряєте лише заміну покажчиків значень у клонованих словниках, яка, очевидно, працює, але ваші тести не вдасться, якщо ви просто зміните властивості на своїх тестових об'єктах, наприклад: dotnetfiddle.net / xmPPKr
Jens

10

Для .NET 2.0 ви могли реалізувати клас, який успадковує Dictionaryта реалізує 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;
    }
}

Потім ви можете клонувати словник, просто зателефонувавши за Cloneметодом. Звичайно, ця реалізація вимагає, щоб тип значень словника реалізувався ICloneable, але в іншому випадку загальна реалізація зовсім не практична.


8

Це добре працює для мене

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

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

Як в коментарях описує Томер Волберг, це не працює, якщо тип значення - це клас, що змінюється.


1
Це серйозно потребує виплат! Якщо початковий словник читається лише, це все одно спрацює: var newDict = readonlyDict.ToDictionary (kvp => kvp.Key, kvp => kvp.Value)
Stephan Ryer

2
Це не спрацьовує, якщо тип значення є змінним класом
Tomer

5

Ви завжди можете використовувати серіалізацію. Ви можете серіалізувати об’єкт, а потім десеріалізувати його. Це дасть вам глибоку копію Словника та всіх елементів у ньому. Тепер ви можете створити глибоку копію будь-якого об'єкта, який позначений як [Serializable], не записуючи спеціального коду.

Ось два методи, які використовуватимуть Бінарну серіалізацію. Якщо ви використовуєте ці методи, ви просто телефонуєте

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

Найкращий спосіб для мене:

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

3
Хіба це не просто копіювання посилання, а не значення, оскільки Словник - це тип посилання? це означає, що якщо ви зміните значення в одному, воно змінить значення в іншому?
Гоку

3

Метод бінарної серіалізації працює чудово, але в моїх тестах він виявився в 10 разів повільніше, ніж несеріалізаційна реалізація клону. Випробував це наDictionary<string , List<double>>


Ви впевнені, що зробили повну глибоку копію? І рядки, і Списки потрібно скопіювати в глибокому вигляді. Є також деякі помилки в версії сериализации , змушуючи його бути повільним: в ToBinary()в Serialize()методі викликається thisзамість yourDictionary. Потім в FromBinary()байт [] спочатку копіюється вручну в MemStream, але він може просто подаватися до його конструктора.
Юпітер

1

Ось що мені допомогло, коли я намагався глибоко скопіювати Словник <рядок, рядок>

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

Удачі


Добре працює для .NET 4.6.1. Це має бути оновлена ​​відповідь.
Таллал Казмі

0

Спробуйте це, якщо ключ / значення 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

Відповідаючи на старий пост, проте мені здалося корисним перекласти його наступним чином:

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;
  }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.