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.
Published
Tuesday, May 23, 2006 2:18 PM
by
AvnerK