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

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

【问题讨论】:

    标签: c# generics collections clone


    【解决方案1】:

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

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

    【讨论】:

      【解决方案2】:

      如果您有一个“对象”字典,并且对象可以是任何类似的东西(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) );
      
      
      

      【讨论】:

        【解决方案3】:

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

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

        祝你好运

        【讨论】:

        • 适用于 .NET 4.6.1。这应该是更新后的答案。
        【解决方案4】:

        这对我来说很好用

         // 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)
        • 如果值类型是可变类就不行
        【解决方案5】:

        回复旧帖子,但我发现将其包装如下很有用:

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

        【讨论】:

          【解决方案6】:

          好的,.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
          【解决方案7】:

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

          【讨论】:

            【解决方案8】:

            对我来说最好的方法是:

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

            【讨论】:

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

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

            您希望副本有多深,以及您使用的是什么版本的 .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 不会复制比较器。您在其他答案中提到了复制比较器,但我认为这个版本的克隆也应该通过比较器。
            【解决方案10】:

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

            【讨论】:

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

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

            【讨论】:

              【解决方案13】:

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

              【讨论】:

                猜你喜欢
                • 2010-11-18
                • 2010-11-19
                • 2011-09-24
                • 2019-08-29
                • 2021-06-29
                • 1970-01-01
                • 2021-03-20
                相关资源
                最近更新 更多