IDictionary<TKey,TValue>, IXmlSerializable, and lambdas

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.


  • Stane

    Your serializable IDictionary solution.
    Hi Chuck, in my search towards an implementable serializable IDictionary I will most probably choose yours. I tried it with my classes and it seems to work. Because the code is quite readable, I think I will be able to change it should it be needed.
    It seems that the code is free, is this so? I will reference the site I found it in a file header, is this enough?
    Is it probable, in your opinion, BTW, that MS would change or exclude the IXmlSerializable interface in future versions? I am working on a comprehensive class library, and would hate it if I need to change the basis in a year or so ;-)

    Otherwise, thanks for your solution
    Stane

  • http://www.deploymentzone.com Chuck

    Oh yes any code I put up here is always free for the taking. It hadn’t even crossed my mind to be honest!

    I find it highly unlikely that they would get rid of IXmlSerializable. Its possible it could be deprecated but I don’t think that’ll happen anytime soon.

    I’m glad you find the code helpful. Hopefully we’ll have a built in .NET Fx solution in the future so this isn’t necessary.

  • hamsterlegs

    Very good!

  • Shimmy

    Hello!

    Thanks for posting your great article.
    I translated a similar VB project.
    Now I have a problem.
    I am trying to set (in a VB proj) in the .settings file the type of a property to the dictionary (i subclassed a non-generic type that inherits Dic<string, List<string>>) and I get an error.
    You might want to help me out, please take a look:
    http://stackoverflow.com/questions/2663836/is-t...