Каков наилучший способ клонирования / глубокого копирования универсального словаря .NET <string, T>?

211

У меня есть общий словарь, Dictionary<string, T>который я хотел бы по существу сделать Clone () из ... любых предложений.

mikeymo
источник

Ответы:

185

Хорошо. .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: Хорошо, если значения не нужно клонировать, «использовать перегрузку конструктора для словаря, который принимает существующий IDictionary» - это хорошо, и уже в моем ответе. Если значения этого нужно клонировать, то ответ вы связаны не помогает вообще.
Джон Скит
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" в этом контексте. Как я добавлю ключ и значение. Это показывает ошибку в моем конце
Pratik
2
@Pratik: Это лямбда-выражения - часть C # 3.
Джон Скит
2
По умолчанию LINQ ToDictionary не копирует компаратор. Вы упомянули копирование компаратора в другом ответе, но я думаю, что эта версия клонирования также должна пройти компаратор.
user420667
86
Dictionary<string, int> dictionary = new Dictionary<string, int>();

Dictionary<string, int> copy = new Dictionary<string, int>(dictionary);
Вестник Смит
источник
5
Указатели значений остаются прежними, если вы примените изменения к значениям в копии, изменения также будут отражены в объекте словаря.
Fokko Driesprong
4
@FokkoDriesprong нет, он просто копирует keyValuePairs в новый объект
17
Это определенно хорошо работает - создает клон как ключа, так и значения. Конечно, это работает только в том случае, если значение НЕ является ссылочным типом, если значение является ссылочным типом, тогда оно фактически принимает только копию ключей в качестве мелкой копии.
Contango
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);

Как описывает Томер Вольберг в комментариях, это не работает, если тип значения является изменяемым классом.

BonifatiusK
источник
1
Это серьезно нуждается в голосовании! Однако если исходный словарь доступен только для чтения, он все равно будет работать: var newDict = readonlyDict.ToDictionary (kvp => kvp.Key, kvp => kvp.Value)
Стефан Райер,
2
Это не работает, если тип значения является изменяемым классом
Томер Вольберг,
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);
nikssa23
источник
3
Разве это не просто копирование ссылки, а не значений, поскольку словарь является ссылочным типом? что означает, что если вы измените значения в одном, это изменит значение в другом?
Гоку
3

Метод двоичной сериализации работает нормально, но в моих тестах он показал, что он в 10 раз медленнее, чем реализация клонов без сериализации. Протестировано наDictionary<string , List<double>>

Loty
источник
Вы уверены, что сделали полную глубокую копию? И строки, и списки должны быть глубоко скопированы. Есть также некоторые ошибки в версии сериализации , заставляя его быть медленным: в ToBinary()в Serialize()методе вызывается thisвместо yourDictionary. Затем в FromBinary()byte [] сначала копируется вручную в 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;
  }
}
Кофе без кофеина
источник