1

I am using the SerializableDictionary defined in this blog entry to store <string, object> data and pass it to/from a WCF service. This works fine if I use value types as the values, because they can be boxed easily to become objects. However, if I use something like

new List<int>() { 5, 10 }, I get an Exception:

The type System.Collections.Generic.List`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] may not be used in this context.

According to the discussion here, I should be using value.GetType() to initialize the XmlSerializer; however, while that lets me serialize, I don't know how to deserialize in a general way back to my SerializableDictionary.

I'm not sure if there's a way to change this cleanly while still allowing <string, object> as my type arguments - I can serialize the value to binary instead of XML and transport it that way (the same code is serializing and deserializing, so I'm not concerned about interoperability), but I would like to have the XML if at all possible.

EDIT

Full code example:

XmlSerializer serializer = new XmlSerializer(typeof(SerializableDictionary<string, object>));
SerializableDictionary<string, object> dic = new SerializableDictionary<string, object>();
dic["test"] = new List<int>() { 5, 10 };
StringBuilder sb = new StringBuilder();
XmlWriter writer = XmlWriter.Create(sb);
serializer.Serialize(writer, dic);
string ser = sb.ToString();

SOLUTION

Thanks to Nico Schertler for giving me the right answer. I'm posting my final code here in case anyone needs it. This is backward-compatible with the original code in the first link, so anything that was serialized by that code can be deserialized by the below.

[XmlRoot("dictionary")]
public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable
{

    #region " IXmlSerializable Members "

    #region " WriteXml "

    public void WriteXml(XmlWriter writer)
    {
        // Base types
        string baseKeyType = typeof(TKey).AssemblyQualifiedName;
        string baseValueType = typeof(TValue).AssemblyQualifiedName;
        writer.WriteAttributeString("keyType", baseKeyType);
        writer.WriteAttributeString("valueType", baseValueType);

        foreach (TKey key in this.Keys)
        {
            // Start
            writer.WriteStartElement("item");

            // Key
            Type keyType = key.GetType();
            XmlSerializer keySerializer = GetTypeSerializer(keyType.AssemblyQualifiedName);

            writer.WriteStartElement("key");
            if (keyType != typeof(TKey)) { writer.WriteAttributeString("type", keyType.AssemblyQualifiedName); }
            keySerializer.Serialize(writer, key);
            writer.WriteEndElement();

            // Value
            TValue value = this[key];
            Type valueType = value.GetType();
            XmlSerializer valueSerializer = GetTypeSerializer(valueType.AssemblyQualifiedName);

            writer.WriteStartElement("value");
            if (valueType != typeof(TValue)) { writer.WriteAttributeString("type", valueType.AssemblyQualifiedName); }
            valueSerializer.Serialize(writer, value);
            writer.WriteEndElement();

            // End
            writer.WriteEndElement();
        }
    }

    #endregion

    #region " ReadXml "

    public void ReadXml(XmlReader reader)
    {
        bool wasEmpty = reader.IsEmptyElement;
        reader.Read();

        if (wasEmpty)
        {
            return;
        }

        // Base types
        string baseKeyType = typeof(TKey).AssemblyQualifiedName;
        string baseValueType = typeof(TValue).AssemblyQualifiedName;

        while (reader.NodeType != XmlNodeType.EndElement)
        {
            // Start
            reader.ReadStartElement("item");

            // Key
            XmlSerializer keySerializer = GetTypeSerializer(reader["type"] ?? baseKeyType);
            reader.ReadStartElement("key");
            TKey key = (TKey)keySerializer.Deserialize(reader);
            reader.ReadEndElement();

            // Value
            XmlSerializer valueSerializer = GetTypeSerializer(reader["type"] ?? baseValueType);
            reader.ReadStartElement("value");
            TValue value = (TValue)valueSerializer.Deserialize(reader);
            reader.ReadEndElement();

            // Store
            this.Add(key, value);

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

    #endregion

    #region " GetSchema "

    public XmlSchema GetSchema()
    {
        return null;
    }

    #endregion

    #endregion

    #region " GetTypeSerializer "

    private static readonly Dictionary<string, XmlSerializer> _serializers = new Dictionary<string, XmlSerializer>();
    private static readonly object _deadbolt = new object();
    private XmlSerializer GetTypeSerializer(string type)
    {
        if (!_serializers.ContainsKey(type))
        {
            lock (_deadbolt)
            {
                if (!_serializers.ContainsKey(type))
                {
                    _serializers.Add(type, new XmlSerializer(Type.GetType(type)));
                }
            }
        }
        return _serializers[type];
    }

    #endregion

}

I'm only writing out the type if it's different than the base type in order to keep the length of the XML down, and I'm keeping a static list of XmlSerializers in order to prevent intantiating them all over the place. I did have to write out the provided types at the beginning in order to be able to prevent writing out the type on each node.

Community
  • 1
  • 1
zimdanen
  • 5,508
  • 7
  • 44
  • 89
  • zimdanen is are you trying to pass / assign array.. of int? is there a typo in what you have shown here `new List() { 5, 10 }` where is the `=` – MethodMan Feb 25 '13 at 17:41
  • @DJKRAZE: I am trying to serialize a `List` using a `SerializableDictionary`. I will add a full code segment to demonstrate the issue. – zimdanen Feb 25 '13 at 17:46

2 Answers2

3

The problem with serialization is that the (de-)serializer needs to know how to process the objects. A serializer for object does not know how to serialize a List<int>.

For serialization you already gave the answer in your question. Use value.GetType() to determine the value's type. Furthermore, you have to save the type itself. This can be achieved easily with the string representation of the type (type.AssemblyQualifiedName).

TValue value = this[key];

var type = value.GetType();
XmlSerializer valueSerializer = new XmlSerializer(type);

writer.WriteStartElement("type");
writer.WriteString(type.AssemblyQualifiedName); 
//you can use FullName if you don't need to address external libraries
writer.WriteEndElement();

writer.WriteStartElement("content");
valueSerializer.Serialize(writer, value);
writer.WriteEndElement();

For deserialization you need to read the type and deserialize the value:

reader.ReadStartElement("value");

reader.ReadStartElement("type");
var typename = reader.ReadContentAsString();
reader.ReadEndElement();
var type = Type.GetType(typename);
XmlSerializer valueSerializer = new XmlSerializer(type);

reader.ReadStartElement("content");
TValue value = (TValue)valueSerializer.Deserialize(reader);
reader.ReadEndElement();

reader.ReadEndElement();
Nico Schertler
  • 32,049
  • 4
  • 39
  • 70
  • Thanks! I added the type as an attribute on the `key` and `value` nodes, which lets me serialize/deserialize without issue. – zimdanen Feb 25 '13 at 19:07
0

in the class that you are referencing in the link do the following

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

namespace sampleLogin
{

    [XmlRoot("dictionary")]
    public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable
    {
        #region IXmlSerializable Members

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

        public void ReadXml(System.Xml.XmlReader reader)
        {
            XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
            XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));
            bool wasEmpty = reader.IsEmptyElement;

            reader.Read();
            if (wasEmpty)
            {
                return;
            }

            while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
            {
                reader.ReadStartElement("item");
                reader.ReadStartElement("key");
                TKey key = (TKey)keySerializer.Deserialize(reader);
                reader.ReadEndElement();
                reader.ReadStartElement("value");
                TValue value = (TValue)valueSerializer.Deserialize(reader);
                reader.ReadEndElement();
                this.Add(key, value);
                reader.ReadEndElement();
                reader.MoveToContent();
            }
            reader.ReadEndElement();
        }



        public void WriteXml(System.Xml.XmlWriter writer)
        {
            XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
            XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));

            foreach (TKey key in this.Keys)
            {
                writer.WriteStartElement("item");
                writer.WriteStartElement("key");
                keySerializer.Serialize(writer, key);
                writer.WriteEndElement();
                writer.WriteStartElement("value");
                TValue value = this[key];
                var type = value.GetType();//new line added here
                valueSerializer = new XmlSerializer(type);//New line added here 
                valueSerializer.Serialize(writer, value);
                writer.WriteEndElement();
                writer.WriteEndElement();
            }
        }
        #endregion
    }
MethodMan
  • 18,625
  • 6
  • 34
  • 52
  • This will serialize but won't deserialize. You need to write out the type in those new lines and use that type in the deserialization. Per your earlier comment, posting my final code in an edit to the question. – zimdanen Feb 25 '13 at 21:29
  • Zimdanen you code was throwing an error in the `public void WriteXml(System.Xml.XmlWriter writer)` Method I have tested that and provided a fix I was not aware of the error in deserialization portion of your code.. – MethodMan Feb 25 '13 at 23:45