Serializing Dictionaries

Tags: .NET, CodeSnippets

Dictionaries have been an annoyance when serializing for a while now. IDictionary in v1.1 and now IDictionary<K,V> in v2.0 are both non-serializable, forcing us to find workarounds or use DataTables or any number of unsatisfactory solutions.

My guess is that is has something to do with the whole buckets/hashcode implementation. If we serialize a dictionary and then deserialize it on a different machine or process, the keys might have a different hashcode - the default hashcode for a reference type is its reference pointer, AFAIK - and the dictionary will be corrupted.

A quick solution for this is to transform the dictionary into a collection of KeyValuePairs, which is a non-hashed straightforward array, and serialize and deserialize that. This means the hashing algorithm runs again when we deserialize, building the new dictionary from scratch.

Usage is then simple:

DictionarySerializer<string, int> serializer = new DictionarySerializer<string,int>();
serializer.Serialize(myDictionary, myStreamWriter);

When implementing this, it turned out that while KeyValuePair<K,V> is marked as Serializable, it actually isn't possible to meaningfully serialize it. This is due to its Key and Value members being read-only, and the XmlSerializer only serializing public members. (More information here).
Due to this, I simply implemented my own internal SerializableKeyValuePair<K,V> struct and use that instead.
The Serialize method now looks something like this:

public void Serialize (IDictionary<K,V> dictionary, StreamWriter serializationStream)
{
   List<SerializableKeyValuePair<K,V>> dictionaryItems = GetKeyValueList(dictionary);
   XmlSerializer ser = new XmlSerializer(typeof(List<SerializableKeyValuePair<K,V>>));
   ser.Serialize(serializationStream, dictionaryItems);
}

Deserialization will follow logically:

public IDictionary<K,V> Deserialize (StreamReader serializationStream)
{
   XmlSerializer ser = new XmlSerializer(typeof(List<SerializableKeyValuePair<K,V>>));
   List<SerializableKeyValuePair<K,V>> dictionaryItems = ser.Deserialize(serializationStream) as List<SerializableKeyValuePair<K,V>>;

   IDictionary<K,V> dictionary = new Dictionary<K,V>(dictionaryItems.Count);
   foreach (SerializableKeyValuePair<K,V> item in dictionaryItems)
   {
      dictionary.Add(item.Key, item.Value);
   }
   
   return dictionary;
}

The problem with making this totally generic is that the serializer can receive a Stream, a TextWriter or an XmlWriter - three classes that do not share any common ancestor. This means we have several overloads doing exactly the same only with different types. A shame.

I've attache the full class for this, slightly more refactored than what I have here. Enjoy.

11 Comments

  • AvnerK said

    I'm not sure I understood your question. The generic Dictionary class accepts two generic arguments. Are you asking about a scenario like Dictionary<string, List>? Where one of my generic arguments is a generic argument in itself? I wouldn't, basically. I would let the List do its own serialization.

  • Martin said

    Ignore previous comment, anything derived from IDictionary apparently cannot be (de)serialized. Another idea for refactoring though: the serializable key value pair struct could be implemented without generic parameters because the encapsulating class already specifies them, thus the struct could simply be non-generic like this: public struct SerializableKeyValuePair { public K Key; public V Value; public SerializableKeyValuePair(KeyValuePair kvp) { this.Key = kvp.Key; this.Value = kvp.Value; } }

  • Wray said

    I think there might be some updates here for .NET 3.5. I am having no problem with a serialized Dictionary<int, List> that I am using in WCF. I think the difference might be a different serializer. When using a DataContract, the NetDataContractSerializer is being used by default. Let's put it this way. I got the values on the server, passed them back through WCF and I got what I started with no problem. The class is not marked Serializable, it is marked DataContract and compiliation does not complain either. So this is probably not an issue with WCF if you don't need to mark a class Serializable as well. This may work with a BinaryFormatter for serialization as well.

Comments have been disabled for this content.