简体   繁体   中英

Serializing lists with an object serializer

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 object s. 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 XmlSerializer s 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.

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

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
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM