如何克隆/深度复制.NET通用字典Dictionary<string, T>的最佳方法?

276

我有一个通用字典 Dictionary<string, T>,我想要创建一个它的副本(Clone()).. 有什么建议吗?

14个回答

282
(注意:尽管克隆版本可能很有用,但对于简单的浅拷贝,我在其他帖子中提到的构造函数是更好的选择。)
你想要进行多深的复制,以及使用哪个版本的.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());

(这些未经测试,但应该可以使用。)

谢谢你的回答,Jon。我实际上正在使用框架的v2.0版本。 - mikeymo
在这个上下文中,“entry => entry.Key,entry => entry.Value”是什么意思?我该如何添加键和值?它在我的端口显示错误。 - Pratik
2
@Pratik:它们是Lambda表达式 - C# 3的一部分。 - Jon Skeet
2
默认情况下,LINQ的ToDictionary不会复制比较器。你在另一个答案中提到了复制比较器,但我认为这个克隆版本也应该传递比较器。 - user420667

207

好的,.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()也是一个合适的深层克隆。


6
@ChrisW:那就是要求每个值都被克隆 - 具体深度或浅度由 Clone() 方法决定。我已经添加了一条相关的注释。 - Jon Skeet
2
如果值不需要被克隆,那么“使用构造函数重载到已存在的IDictionary”的方法是可以的,而且已经在我的答案中提到了。如果值需要被克隆,那么你链接的答案就没有帮助了。 - Jon Skeet
1
@SaeedGanji:没问题,是的。(当然,如果结构体包含对可变引用类型的引用,那可能仍然是个问题……但希望不是这种情况。) - Jon Skeet
使用构造函数重载到已存在的IDictionary的Dictionary是可以的。这也是线程安全的,对吧?我不需要在克隆时添加锁对象吗? - Saeed Ganji
1
@SaeedGanji:这取决于其他操作。如果其他线程只是从原始字典中读取,那么我认为应该没问题。如果有任何修改,您需要在那个线程和克隆线程中都进行锁定,以避免它们同时发生。如果您想在使用字典时实现线程安全,请使用ConcurrentDictionary - Jon Skeet
显示剩余6条评论

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

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

9
如果你对副本中的值进行更改,指针仍然指向同样的值,这些更改也会反映在字典对象中。 - Fokko Driesprong
5
不会,它只是将keyValuePairs复制到新对象中。 - user1625871
29
这绝对有效 - 它创建了键和值的克隆。当然,这仅适用于值不是引用类型的情况,如果值是引用类型,则它只是浅复制键。 - Contango
1
@Contango 所以在这种情况下,由于字符串和整数不是引用类型,它会起作用对吧? - Furkan Gözükara
3
@UğurAldanmaz,您忘记测试引用对象的实际更改,您仅测试克隆字典中值指针的替换,这显然有效,但如果您只更改测试对象的属性,例如:https://dotnetfiddle.net/xmPPKr,则您的测试将失败。 - Jens
显示剩余4条评论

37

当我尝试深度复制一个Dictionary<string, string>时,这就是帮助我的方法。

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

祝你好运


3
适用于.NET 4.6.1,这应该是更新后的答案。 - Tallal Kazmi
14
这将创建一个浅拷贝。如果dict2的值是引用类型,那么dict的值也会被dict2更新。(基于.NET5) - help
@help 字符串对象是不可变的,代码没问题。 - Aminos

11

对于 .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 Møller
4
如果值的类型是可变类,那么这种方法就行不通。 - Tomer Wolberg

7

您可以使用序列化。您可以对对象进行序列化,然后进行反序列化。这将为您提供Dictionary和其中所有项的深层副本。现在,您可以创建任何标记为[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;
}

7
我认为最好的方法是这样的:
Dictionary<int, int> copy= new Dictionary<int, int>(yourListOrDictionary);

4
这不就是复制引用而非值吗?因为字典是引用类型,这意味着如果你在一个字典中改变了某个键值对的值,那么它也会改变另一个字典中相应键值对的值。 - Goku
1
不,它正在创建一个“新的”字典(请注意关键词“new”),并用旧对象的值填充它。对于<int,int>来说,这是一个完全独立的副本。 - Sinus the Tentacular
1
这将创建一个浅拷贝。如果字典的值是引用类型,那么只会复制地址值。 - help

4

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


你确定你进行了完整的深度复制吗?字符串和列表都需要进行深度复制。此外,在序列化版本中存在一些错误,导致它变得很慢:在 ToBinary() 中,Serialize() 方法使用 this 而不是 yourDictionary 进行调用。然后在 FromBinary() 中,byte[] 首先手动复制到 MemStream 中,但实际上可以直接提供给其构造函数。 - Jupiter

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接