SerializableDictionary

Das muss doch irgendwie gehen!

esskar über Programmierung

Wer sich schoneinmal mit Serialisierung in .NET beschäftigt hat, stolpert früher oder später über folgende Exception:

System.NotSupportedException: The type System.Collections.Generic.Dictionary is not supported because it implements IDictionary.

Wenn jetzt anfängt zu googeln, stolpert man irgendwann über folgenden Ansatz. Meiner Meinung ist es nur ein Ansatz, da man bei abgeleiteten Typen (also class B : A { }) keine Chance hat, wenn man SerializableDictionary<int, A> serialisieren will, dort aber eben auch Objekte vom Typ B reinsteckt. Über SerializableDictionary<int, object> reden wir erstmal nicht.

Um dies zu lösen, muss man also auch (ggf. pro key und value) einen Typ angeben. Ich schreibe “ggf.” mit Absicht, weil wenn der Typ kein abgeleiter Typ ist, braucht es keine Typ-Angabe und man spart Platz im XML.

Hier also meine vollständige Lösung:


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

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;
        }

        private T ReadXmlOne(string tag, System.Xml.XmlReader reader)
        {
            T retval = default(T);

            reader.MoveToContent();

            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);

            XmlSerializer serializer = new XmlSerializer(type);
            retval = (T)serializer.Deserialize(reader);

            reader.ReadEndElement();

            return retval;
        }

        public void ReadXml(System.Xml.XmlReader reader)
        {
            bool wasEmpty = reader.IsEmptyElement;
            reader.Read();
            if (wasEmpty) return;

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

                var key = this.ReadXmlOne("key", reader);
                var val = this.ReadXmlOne("value", reader);
                this.Add(key, val);

                reader.ReadEndElement(); // 

                reader.MoveToContent();
            }

            reader.ReadEndElement();
        }

        private void WriteXmlOne(string tag, T obj, System.Xml.XmlWriter writer)
        {
            Type type = obj.GetType();
            XmlSerializer serializer = new XmlSerializer(type);

            writer.WriteStartElement(tag);
            if(!typeof(T).Equals(type))
                writer.WriteAttributeString("type", type.AssemblyQualifiedName);
            serializer.Serialize(writer, obj);
            writer.WriteEndElement();
        }

        public void WriteXml(System.Xml.XmlWriter writer)
        {
            foreach (var v in this)
            {
                writer.WriteStartElement("item");

                this.WriteXmlOne("key", v.Key, writer);
                this.WriteXmlOne("value", v.Value, writer);
                              
                writer.WriteEndElement(); // 
            }
        }

        #endregion
    }
}

Ein kleiner Test ist auch schnell geschrieben


public class Thing {
   public int Id { get; set; }
}

public class Car : Thing {
   public string Name { get; set; }
}

SerializableDictionary s = new SerializableDictionary();
for (int i = 0; i < 10; i++)
	s.Add(i, new Car() { Id = i * 100, Name = "Test" + i });

XmlSerializer xs = new XmlSerializer(s.GetType());

StringBuilder sb = new StringBuilder();
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
using (XmlWriter writer = XmlWriter.Create(sb, settings))
{
	xs.Serialize(writer, s);
}

Console.WriteLine(sb.ToString());

SerializableDictionary d = null;
using (XmlReader reader = XmlReader.Create(new StringReader(sb.ToString())))
{
	d = (SerializableDictionary)xs.Deserialize(reader);
}

Assert.AreEqual(s.Count, d.Count);

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