克隆/深复制.NET通用Dictionary <string,T>的最佳方法是什么?


Answers:


184

好的,.NET 2.0回答:

如果您不需要克隆值,则可以使用Dictionary的构造函数重载,它使用现有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值可能是另一个[sub]集合。
ChrisW

6
@ChrisW:好吧,这是要克隆每个值-取决于Clone()方法是深还是浅。我为此添加了注释。
乔恩·斯基特

1
@SaeedGanji:好吧,如果不需要克隆值,那么“使用带有现有IDictionary的Dictionary的构造函数重载”就可以了,已经在我的回答中了。如果确实需要克隆值,那么链接到的答案根本没有帮助。
乔恩·斯基特

1
@SaeedGanji:很好,是的。(当然,如果结构包含对可变引用类型的引用,那仍然可能是个问题……但希望不是这种情况。)
Jon Skeet

1
@SaeedGanji:这取决于发生了什么。如果其他线程仅从原始词典中读取,那么我认为应该没问题。如果有任何修改,您将需要同时锁定该线程和克隆线程,以避免它们同时发生。如果在使用字典时希望线程安全,请使用ConcurrentDictionary
乔恩·斯基特

210

(注意:尽管克隆版本可能有用,但是对于简单的浅表副本,我在另一篇文章中提到的构造函数是一个更好的选择。)

您希望该副本有多深,以及您正在使用什么版本的.NET?我怀疑如果使用.NET 3.5,对ToDictionary的LINQ调用(同时指定键和元素选择器)将是最简单的方法。

例如,如果您不介意值是浅表克隆:

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:他们是lambda表达式- C#3的一部分
乔恩斯基特

2
默认情况下,LINQ的ToDictionary不复制比较器。您在另一个答案中提到过复制比较器,但是我认为此版本的克隆也应该通过比较器。
user420667 '16

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

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

5
值的指针仍然相同,如果将更改应用于副本中的值,则更改也将反映在字典对象中。
Fokko Driesprong'4

3
@FokkoDriesprong不,不会,它只是将keyValuePairs复制到新对象中

17
这肯定很好用-它创建键和值的副本。当然,这仅在值不是引用类型的情况下才有效,如果值是引用类型,则它实际上仅将键的副本作为其浅表副本。
康坦戈2014年

1
@Contango所以在这种情况下,因为string和int不是引用类型,它将正常工作吗?
MonsterMMORPG's

3
@UğurAldanmaz您忘了测试对引用对象的实际更改,只测试了显然有用
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);

正如Tomer Wolberg在评论中描述的那样,如果值类型是可变的类,则这将不起作用。


1
这严重需要投票!但是,如果原始词典是只读的,则仍然可以使用:var newDict = readonlyDict.ToDictionary(kvp => kvp.Key,kvp => kvp.Value)
Stephan Ryer

2
如果值类型是可变的类,则无法使用
Tomer Wolberg,

5

您可以始终使用序列化。您可以序列化对象,然后反序列化它。这将为您提供《词典》及其中所有项目的详尽副本。现在,您可以创建任何标记为[可序列化]的对象的深层副本,而无需编写任何特殊代码。

这是将使用二进制序列化的两种方法。如果使用这些方法,则只需调用

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
这不是复制引用,而不是值,因为Dictionary是引用类型?这意味着如果您更改其中一个的值,将会更改另一个的值?
悟空

3

二进制序列化方法可以正常工作,但是在我的测试中,它显示出比非串行化克隆慢10倍。测试了Dictionary<string , List<double>>


您确定执行了完整的深复制吗?字符串和列表都需要进行深度复制。也有序列化版本的一些bug导致它是缓慢的:在ToBinary()Serialize()方法被调用this,而不是yourDictionary。然后在FromBinary()byte []中首先将其手动复制到MemStream,但可以将其提供给其构造函数。
木星

1

当我尝试深度复制Dictionary <string,string>时,这就是帮助我的原因

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

祝好运


适用于.NET 4.6.1。这应该是更新的答案。
塔拉尔·卡兹米

0

如果键/值是可克隆的,请尝试以下操作:

    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;
  }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.