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

Posted

技术标签:

【中文标题】克隆/深度复制 .NET 通用 Dictionary<string, T> 的最佳方法是啥?【英文标题】:What is the best way to clone/deep copy a .NET generic Dictionary<string, T>?克隆/深度复制 .NET 通用 Dictionary<string, T> 的最佳方法是什么? 【发布时间】:2010-09-13 11:12:07 【问题描述】:

我有一个通用字典Dictionary&lt;string, T&gt;,我想从本质上提出一个 Clone() 的 ..any 建议。

【问题讨论】:

【参考方案1】:

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

您希望副本有多深,以及您使用的是什么版本的 .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。 在这种情况下,什么是“entry => entry.Key,entry => entry.Value”。我将如何添加键和值。它在我结束时显示错误 @Pratik:它们是 lambda 表达式 - C# 3 的一部分。 默认情况下 LINQ 的 ToDictionary 不会复制比较器。您在其他答案中提到了复制比较器,但我认为这个版本的克隆也应该通过比较器。【参考方案2】:

好的,.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 值可能是另一个 [子] 集合。 @ChrisW:那是要求克隆每个值——这取决于Clone() 方法是深还是浅。我已经为此添加了注释。 @SaeedGanji:好吧,如果不需要克隆值,那么“使用构造函数重载到接受现有 IDictionary 的 Dictionary ”很好,并且已经在我的回答中。如果值 do 需要被克隆,那么您链接到的答案根本没有帮助。 @SaeedGanji:应该没问题,是的。 (当然,如果结构包含对可变引用类型的引用,那仍然可能是个问题……但希望情况并非如此。) @SaeedGanji:这取决于其他情况。如果其他线程只是从原始字典读取,那么我相信它应该没问题。如果有任何东西正在修改它,您需要锁定该线程和克隆线程,以避免它们同时发生。如果您在使用字典时想要线程安全,请使用ConcurrentDictionary【参考方案3】:
Dictionary<string, int> dictionary = new Dictionary<string, int>();

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

【讨论】:

值的指针还是一样的,如果你对copy中的值应用更改,更改也会反映在字典对象中。 @FokkoDriesprog 不,它只是将 keyValuePairs 复制到新对象中 这绝对有效 - 它创建了键和值的克隆。当然,这只有在值不是引用类型时才有效,如果值是引用类型,那么它实际上只将键的副本作为其浅拷贝。 @Contango 所以在这种情况下,由于 string 和 int 不是引用类型,它会正常工作吗? @UğurAldanmaz 您忘记测试对引用对象的实际更改,您只测试克隆字典中值指针的替换,这显然有效,但如果您只是更改测试中的属性,您的测试将失败对象,例如:dotnetfiddle.net/xmPPKr【参考方案4】:

这对我有帮助,当我尝试深度复制 Dictionary

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

祝你好运

【讨论】:

适用于 .NET 4.6.1。这应该是更新后的答案。【参考方案5】:

对于 .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,否则泛型实现根本不实用。

【讨论】:

【参考方案6】:

这对我来说很好用

 // 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 在 cmets 中所描述的,如果值类型是可变类,这将不起作用。

【讨论】:

这真的需要点赞!但是,如果原始字典是只读的,这仍然有效: var newDict = readonlyDict.ToDictionary(kvp => kvp.Key, kvp => kvp.Value) 如果值类型是可变类就不行【参考方案7】:

您总是可以使用序列化。您可以序列化对象然后反序列化它。这将为您提供字典的深层副本以及其中的所有项目。现在您可以创建任何标记为 [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;

【讨论】:

【参考方案8】:

对我来说最好的方法是:

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

【讨论】:

这不只是复制引用而不是复制值,因为 Dictionary 是引用类型吗?这意味着如果你改变一个值,它会改变另一个值? 不,它正在创建一个new 字典(注意关键字“new”)并用旧对象的值填充它。对于 它是一个完全独立的副本。【参考方案9】:

二进制序列化方法工作正常,但在我的测试中,它显示比克隆的非序列化实现慢 10 倍。在Dictionary&lt;string , List&lt;double&gt;&gt;上测试过

【讨论】:

你确定你做了一个完整的深拷贝吗?字符串和列表都需要深度复制。序列化版本中也有一些错误导致它变慢:在ToBinary() 中,Serialize() 方法是用this 而不是yourDictionary 调用的。然后在FromBinary() 中,首先将 byte[] 手动复制到 MemStream 但它可以只提供给它的构造函数。【参考方案10】:

如果键/值是 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;
    

【讨论】:

【参考方案11】:

如果您有一个“对象”字典,并且对象可以是任何类似的东西(double、int、...或 ComplexClass):

Dictionary<string, object> dictSrc  get; set; 

public class ComplexClass : ICloneable

    
    private Point3D ...;
    private Vector3D ....;
    [...]

    public object Clone()
    
        ComplexClass clone = new ComplexClass();
        clone = (ComplexClass)this.MemberwiseClone();
        return clone;
    




dictSrc["toto"] = new ComplexClass()
dictSrc["tata"] = 12.3
...

dictDest = dictSrc.ToDictionary(entry => entry.Key,
                                entry => ((entry.Value is ICloneable) ? (entry.Value as ICloneable).Clone() : entry.Value) );


【讨论】:

【参考方案12】:

这是一些真正的“真正的深度复制”,不知道类型,但有一些递归遍历,适合初学者。它适用于嵌套类型和我认为几乎所有棘手的类型。我还没有添加嵌套数组处理,但是您可以根据自己的选择进行修改。

Dictionary<string, Dictionary<string, dynamic>> buildInfoDict =
    new Dictionary<string, Dictionary<string, dynamic>>()
    
        "tag",new Dictionary<string,dynamic>()
                  "attrName", "tag"  ,
                  "isCss", "False"  ,
                "turnedOn","True" ,
                  "tag",null 
             ,
        "id",new Dictionary<string,dynamic>()
                  "attrName", "id"  ,
                  "isCss", "False"  ,
                "turnedOn","True" ,
                  "id",null 
             ,
                "width",new Dictionary<string,dynamic>()
                  "attrName", "width"  ,
                  "isCss", "True"  ,
                "turnedOn","True" ,
                  "width","20%" 
             ,
                "height",new Dictionary<string,dynamic>()
                  "attrName", "height"  ,
                  "isCss", "True"  ,
                "turnedOn","True" ,
                  "height","20%" 
             ,
                "text",new Dictionary<string,dynamic>()
                  "attrName", null  ,
                  "isCss", "False"  ,
                "turnedOn","True" ,
                  "text","" 
             ,
                "href",new Dictionary<string,dynamic>()
                  "attrName", null  ,
                  "isCss", "False"  ,
                  "flags", "removeAttrIfTurnedOff"  ,
                "turnedOn","True" ,
                  "href","about:blank" 
             
    ;

var cln=clone(buildInfoDict);

public static dynamic clone(dynamic obj)

    dynamic cloneObj = null;
    if (IsAssignableFrom(obj, typeof(IDictionary)))
    
        cloneObj = Activator.CreateInstance(obj.GetType());
        foreach (var key in obj.Keys)
        
            cloneObj[key] = clone(obj[key]);
        

    
    else if (IsNumber(obj) || obj.GetType() == typeof(string))
    
        cloneObj = obj;
    
    else
    
        Debugger.Break();
    
    return cloneObj;



public static bool IsAssignableFrom(this object obj, Type ObjType = null, Type ListType = null, bool HandleBaseTypes = false)

    if (ObjType == null)
    
        ObjType = obj.GetType();
    

    bool Res;

    do
    
        Res = (ObjType.IsGenericType && ObjType.GetGenericTypeDefinition().IsAssignableFrom(ListType)) ||
            (ListType == null && ObjType.IsAssignableFrom(obj.GetType()));
        ObjType = ObjType.BaseType;
     while ((!Res && ObjType != null) && HandleBaseTypes && ObjType != typeof(object));

    return Res;


public static bool IsNumber(this object value)

    return value is sbyte
            || value is byte
            || value is short
            || value is ushort
            || value is int
            || value is uint
            || value is long
            || value is ulong
            || value is float
            || value is double
            || value is decimal;

【讨论】:

以上是关于克隆/深度复制 .NET 通用 Dictionary<string, T> 的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

Java:深度克隆/复制实例的推荐解决方案

反射实现java深度克隆

列表有更好的深度克隆方法吗? [复制]

在Java中深度克隆多维数组......? [复制]

.Net 深度克隆——最好的方法是啥?

关于JavaScript对象深度克隆