65.9K
CodeProject 正在变化。 阅读更多。
Home

深度复制字典例程

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.24/5 (4投票s)

2007年12月18日

CPOL

2分钟阅读

viewsIcon

51754

downloadIcon

155

克隆一个字典。

问题

我今天在调试一些智能客户端代码,想知道为什么表的本地缓存客户端重新连接到服务器返回空行。除了偶然发现明显的错误(缓存不应该被更新),我还修复了第一个错误的来源。

我执行的代码是

Dictionary<string, string> parms = new Dictionary<string, string>();

parms["@UID"] = uid;
DataView dv = api.LoadViewIntoContainer("PhysicalKiosk", "ComputerName=@UID", parms);

parms["@UID"] = physicalKioskId;
DataView dv2 = api.LoadViewIntoContainer("Kiosk", "PhysicalKioskId=@UID", parms);

现在,因为这些视图被设置为缓存(因此客户端可以在没有服务器连接的情况下加载数据),我的客户端API忠实地保存所有信息,以便稍后重建此视图,包括参数列表,该参数列表存储在ViewInfo类中。 这是构造函数,简化为基本问题

public ViewInfo(Dictionary<string, string> parms)
{
  this.parms = parms;
}

所以,你看到问题了吗?

当我加载第二个DataView时,我重复使用parms字典,因此与第一个DataView关联的ViewInfoparms变量会使用新值更新! 这是因为ViewInfo存储了对parms字典的引用,所以当字典更改时,对该字典的所有引用都会看到该更改。

解决方案

显而易见的解决方案是,创建一个新的parms实例,以便ViewInfo实例获得字典的唯一实例。 考虑到这一点,我意识到我也会在许多其他地方忘记这样做,所以为什么不在ViewInfo构造函数中进行一些防御性编程并克隆字典呢。 经过一番搜索,我没有找到任何克隆字典的示例代码,所以我写了一个。 以下代码实现了两种类型的克隆

  1. 如果键和值是值类型或字符串,则“手动”复制字典
  2. 如果键或值不是值类型,则将字典序列化为内存流,然后反序列化,从而有效地执行深层复制

代码说明了这一点

public static Dictionary<K, V> CloneDictionary<K, V>(Dictionary<K, V> dict)
{
  Dictionary<K, V> newDict=null;

  // The clone method is immune to the source dictionary being null.
  if (dict != null)
  {
    // If the key and value are value types, clone without serialization.
    if (((typeof(K).IsValueType || typeof(K) == typeof(string)) &&
         (typeof(V).IsValueType) || typeof(V) == typeof(string)))
    {
      newDict = new Dictionary<K, V>();
      // Clone by copying the value types.
      foreach (KeyValuePair<K, V> kvp in dict)
      {
        newDict[kvp.Key] = kvp.Value;
      }
    }
    else
    {
      // Clone by serializing to a memory stream, then deserializing.
      // Don't use this method if you've got a large objects, as the
      // BinaryFormatter produces bloat, bloat, and more bloat.
      BinaryFormatter bf = new BinaryFormatter();
      MemoryStream ms = new MemoryStream();
      bf.Serialize(ms, dict);
      ms.Position = 0;
      newDict = (Dictionary<K, V>)bf.Deserialize(ms);
    }
  }

  return newDict;
}

您还会注意到克隆例程处理dict参数为null的情况。 我认为它应该 - 克隆 null 应该返回 null。 另请注意,这是一个泛型方法与泛型相比的一个很好的例子。

修复后的构造函数现在看起来像这样(作为一个好的用例)

public ViewInfo(Dictionary<string, string> parms)
{
  // Clone the dictionary so that we get a snapshot independent of the application usage.
  // This fixes a problem where, for example, a parameter is reused for a different view.
  // Without cloning, this would alter the dictionary key-value of a previous use case.
  this.parms = Clone.CloneDictionary<string, string>(parms);
}

结论

在我的API核心代码中遇到这个错误是相当令人尴尬的,但它清楚地说明了注意保存对集合的引用是多么重要,以及很容易陷入麻烦,通常是在很久以后,并且通过一个简单的怪癖,试图变得聪明并重复使用一些代码的其他部分存储的集合。

© . All rights reserved.