The greatest problem I have encountered with the .NET framework (through 3.5) is that Dictionary instances are not serializable. You have to write custom serialization routines and its different if you are doing binary or XML serialization.
An important aspect to XML serialization to me is readability. If I'm serializing something to XML then I expect it to be readable and I prefer less nodey to more. I like to use attributes whenever possible.
I originally used some somewhat dated code I found on Matt Berther's blog and while it worked, it gave me the nodey version I didn't care much for:
<dictionary> <item> <key>type</key> <value>resource</value> </item> <item> <key>version</key> <value>1</value> </item> </dictionary>
The value of the nodey version is that when the types represented by TKey and TValue are themselves serializable in a way that can allow them to be represented by their own XML object graphs. Ideally I wanted a format that could have output like:
<item key="type" value="resource" />
but still fall back to the more advanced nodey version as necessary, and mix and match (TKey could be a string while TValue could be a more complex serializable type, such as a domain class with many properties).
My solution was to establish what classes could be attributable:
public class Dictionary<TKey,TValue> : IDictionary<TKey, TValue>, ISerializable, IDictionary, IXmlSerializable
...
private readonly static List<Type> _attributableTypes;
static Dictionary() { _attributableTypes = new List<Type> { typeof(Boolean), typeof(Byte), typeof(Char), typeof(DateTime), typeof(Decimal), typeof(Double), typeof(Enum), typeof(Guid), typeof(Int16), typeof(Int32), typeof(Int64), typeof(SByte), typeof(Single), typeof(String), typeof(TimeSpan), typeof(UInt16), typeof(UInt32), typeof(UInt64) }; }
private static bool IsAttributable(Type t) { return _attributableTypes.Contains(t); }
And the meat of the code, the IXmlSerializable interface implementation:
System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() { return null; } void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) { // some types can be stored easily as attributes while others // require their own XML rendering Func<TKey> readKey; Func<TValue> readValue; var isAttributable = new { Key = IsAttributable(typeof(TKey)), Value = IsAttributable(typeof(TValue)) }; // keys if (isAttributable.Key) { readKey = () => (TKey)Convert.ChangeType( reader.GetAttribute("key"), typeof(TKey) ); } else { var keySerializer = new XmlSerializer(typeof(TKey)); readKey = () => { while (reader.Name != "key") reader.Read(); reader.ReadStartElement("key"); var key = (TKey)keySerializer.Deserialize(reader); reader.ReadEndElement(); return key; }; } // values if (isAttributable.Value && isAttributable.Key) { readValue = () => (TValue)Convert.ChangeType( reader.GetAttribute("value"), typeof(TValue) ); } else { var valueSerializer = new XmlSerializer(typeof(TValue)); readValue = () => { while (reader.Name != "value") reader.Read(); reader.ReadStartElement("value"); var value = (TValue)valueSerializer.Deserialize(reader); reader.ReadEndElement(); return value; }; } var wasEmpty = reader.IsEmptyElement; reader.Read(); if (wasEmpty) return; while (reader.NodeType != System.Xml.XmlNodeType.EndElement) { while (reader.NodeType == System.Xml.XmlNodeType.Whitespace) reader.Read(); var key = readKey(); var value = readValue(); Add(key, value); if (!isAttributable.Key || !isAttributable.Value) reader.ReadEndElement(); else reader.Read(); while (reader.NodeType == System.Xml.XmlNodeType.Whitespace) reader.Read(); } reader.ReadEndElement(); } void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer) { Action<TKey> writeKey; Action<TValue> writeValue; var isAttributable = new { Key = IsAttributable(typeof(TKey)), Value = IsAttributable(typeof(TValue)) }; if (isAttributable.Key) { writeKey = v => writer.WriteAttributeString("key", v.ToString() ); } else { var keySerializer = new XmlSerializer(typeof(TKey)); writeKey = v => { writer.WriteStartElement("key"); keySerializer.Serialize(writer, v); writer.WriteEndElement(); }; } // when keys aren't attributable, neither are values if (isAttributable.Value && isAttributable.Key) { writeValue = v => writer.WriteAttributeString("value", v.ToString() ); } else { var valueSerializer = new XmlSerializer(typeof(TValue)); writeValue = v => { writer.WriteStartElement("value"); valueSerializer.Serialize(writer, v); writer.WriteEndElement(); }; } foreach (var key in Keys) { writer.WriteStartElement("item"); writeKey(key); writeValue(this[key]); writer.WriteEndElement(); } }
Bonus: I also learned that multiline lambdas exist and I snuck in an anonymous type to boot. I like that I was able to take two separate approaches to XML serialization, distill their interfaces from their inner workings, and then just expose the correct method signature within a method to keep the iterative loop clean of if/else logic (even if I just moved the logic above the loop). To me this code seems much more readable than if I had kept everything in the loop.
Update: it helps to post working code. Also, here's a zip file with the implementation and some brief NUnit flavored tests.