SerializableDictionary – Teil 2 nach 1

…der 2. Teil der Reise

esskar über Programmierung

Wir erinnern uns an den 1. Teil: gesucht wurde eine Lösung, ein Object vom Typ System.Collections.Generic.Dictionary zu serialisieren; und auch gefunden. Auf geht’s zu SerializableDictionary – Teil 2.
Beim näheren Hinsehen wurden einige Unschönheiten deutlich: ein einzelnes KeyValue Paar wurde folgendermaßen serialisiert:


<item>
   <key>
      <string>Age</string>
   </key>
   <value type="System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <int>12</int>
   </value>
</item>

Was meiner Meinung nach stört, ist bzw. noch deutlicher ; also die Type-Angabe in Tag-Form, vorallem dann, wenn wir wie bei type=“System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089″ den eindeutigen Typ gleich mitliefern. Besser wäre also folgendes Endergebnis:


<item>
   <key>Age</key>
   <value type="System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">12</value>
</item>

Dies kann durch ein paar kleine Änderungen erledigt werden. Zunächst legen wir uns aber eine Helperklasse an


using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;

namespace UswUsf 
{
   public static class SerializationHelper
   {
       public static void SerializeValue(XmlWriter writer, string tag, T obj)
       {
            // do not serialize Null Values.
            if (obj == null)
                obj = default(T);
            if (obj == null)
                return;
            Type type = obj.GetType();
            writer.WriteStartElement(tag);
            if (!typeof(T).Equals(type))
                writer.WriteAttributeString("type", type.AssemblyQualifiedName);
            // instead of using an own XmlSerializer to serialize the object
            // we use XmlWriter.WriteValue to serialize the value
            // remember: we already know the type as we put it as an attribute
            writer.WriteValue(obj);           
            writer.WriteEndElement();
       }
   }
}

Die Deserialisierung sieht folgendermaßen aus:


public static T DeserializeValue(XmlReader reader, string tag)
{
    T retval = default(T);

    reader.MoveToContent();
    if (reader.LocalName.Equals(tag))
    {
        string typeNameAttr = reader.GetAttribute("type");
        if (string.IsNullOrEmpty(typeNameAttr))
            typeNameAttr = typeof(T).AssemblyQualifiedName;

        Type type = Type.GetType(typeNameAttr, true, true);
        if (!typeof(T).IsAssignableFrom(type))
        {
            string msg = string.Format("Unable to assign {0} from {1}",
                  typeof(T), type);
            throw new InvalidOperationException(msg);
        }

        reader.ReadStartElement(tag);
        // auch hier nutzen wir die Möglichkeit des Readers, 
        // Content als einen bestimmten Type zu lesen
        retval = (T)reader.ReadContentAs(type, null);
        reader.ReadEndElement();
    }

    return retval;
}

Um die komplette De- bzw. Serialisierung weiter zu kaspeln, legen wir 2 weitere Funktionen an.


public static void SerializeDictionary(XmlWriter writer, IDictionary dictionary)
{
    foreach (var v in dictionary)
    {
        writer.WriteStartElement("item");

        SerializationHelper.SerializeValue(writer, "key", v.Key);
        SerializationHelper.SerializeValue(writer, "value", v.Value);

        writer.WriteEndElement(); // 
    }
}

public static void DeserializeDictionary(XmlReader reader, IDictionary dictionary)
{
    bool wasEmpty = reader.IsEmptyElement;
    reader.Read();
    if (wasEmpty) return;

    reader.MoveToContent();
    while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
    {
        reader.ReadStartElement("item");

        var key = SerializationHelper.DeserializeValue(reader, "key");
        if (key.Equals(default(TKey))) continue;
        var val = SerializationHelper.DeserializeValue(reader, "value");

        dictionary.Add(key, val);

        reader.ReadEndElement(); // 
        reader.MoveToContent();
    }

    reader.ReadEndElement();
}

Dadurch wirkt unsere „neue“ SerializableDictionary Klasse nun recht kompakt:


namespace UswUsf
{
    [XmlRoot("dictionary")]
    public class SerializableDictionary : Dictionary, IXmlSerializable
    {
        public SerializableDictionary() : base() { }

        public SerializableDictionary(IEqualityComparer comparer) 
            : base(comparer) { }

        #region IXmlSerializable Members

        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return null;
        }        

        public void ReadXml(XmlReader reader)
        {
            SerializationHelper.DeserializeDictionary(reader, this);
        }
        
        public void WriteXml(XmlWriter writer)
        {
            SerializationHelper.SerializeDictionary(writer, this);
        }

        #endregion        
    }
}

Informationen über den Autor

esskar

ist das, was man einen Fullstack Developer bezeichnet. Vom Design bis in die Tiefen der C-Programmierung verfügt er nationale und internationale Projekterfahrung, und alles unter dem Motto — Code ist Poesie. Follow @esskar