Click here to Skip to main content
15,885,244 members
Articles / Programming Languages / C#
Article

A Deep Dish Dictionary Clone Routine

Rate me:
Please Sign up or sign in to vote.
4.24/5 (4 votes)
18 Dec 2007CPOL2 min read 50.3K   141   13   8
Cloning a Dictionary.

The Problem

I was debugging some Smart Client code today, wondering why the local cache of a table was returning empty rows after the client reconnected with the server. Besides stumbling into the obvious bug (the cache wasn't supposed to be updated), I also fixed the source of the first bug.

The code I was executing was:

C#
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);

Now, because these views are set up as cached (so the client can load the data without the server being connected), my client API dutifully saves all the information to reconstruct this view later on, including the parameter list, which is stored in a ViewInfo class. Here's the constructor, reduced to the essential issue:

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

So, do you see the problem?

When I load the second DataView, I'm reusing the parms dictionary, so the ViewInfo's parms variable associated with the first DataView gets updated with the new value! This is because the ViewInfo is storing a reference to the parms dictionary, so when the dictionary changes, all references to that dictionary see that change.

The Solution

The obvious solution was, create a new parms instance so that the ViewInfo instance gets a unique instance of the dictionary. Thinking about this, I realized I'm going to forget to do this in many other places as well, so why not do a bit of defensive programming and, in the ViewInfo constructor, clone the dictionary. After a brief search, I didn't find any sample code that clones a dictionary, so I wrote one. The following code implements two types of cloning:

  1. if the key and value are value types or strings, the dictionary is copied "by hand"
  2. if the key or value are not value types, then the dictionary is serialized to a memory stream, then deserialized, effectively performing a deep copy

The code illustrates this:

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

You'll also note that the clone routine handles the case where the dict parameter is null. I figure it ought to--cloning a null should return a null. Also note that this is a good example of a generic method as compared to a generic class.

The fixed constructor now looks like this (as a good usage case):

C#
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);
}

Conclusion

It's rather embarrassing to have encountered this bug in the core code of my API, but it clearly illustrates how important it is to be conscious of saving references to collections and how easy it is to get into trouble, often much later, and by a simple quirk of trying to be smart and reusing something like a collection that is stored by some other part of the code.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Interacx
United States United States
Blog: https://marcclifton.wordpress.com/
Home Page: http://www.marcclifton.com
Research: http://www.higherorderprogramming.com/
GitHub: https://github.com/cliftonm

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions

 
GeneralFull deep cloning Pin
Igor Velikorossov18-Dec-07 11:33
Igor Velikorossov18-Dec-07 11:33 
GeneralRe: Full deep cloning Pin
Marc Clifton18-Dec-07 16:40
mvaMarc Clifton18-Dec-07 16:40 
GeneralRe: Full deep cloning Pin
Aaron Jackson21-Dec-07 14:23
Aaron Jackson21-Dec-07 14:23 
GeneralRe: Full deep cloning Pin
peterchen21-Dec-07 21:59
peterchen21-Dec-07 21:59 
GeneralRe: Full deep cloning Pin
Marc Clifton22-Dec-07 3:17
mvaMarc Clifton22-Dec-07 3:17 
GeneralICloneable should not be implemented [modified] Pin
Rama Krishna Vavilala22-Dec-07 3:11
Rama Krishna Vavilala22-Dec-07 3:11 
GeneralRe: ICloneable shoudl not be implemented Pin
Marc Clifton22-Dec-07 3:18
mvaMarc Clifton22-Dec-07 3:18 
GeneralRe: ICloneable shoudl not be implemented Pin
peterchen22-Dec-07 3:45
peterchen22-Dec-07 3:45 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.