C# Serializable Dictionary – a Working Example

Here is the example:

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

[Serializable()]
public class SerializableDictionary<TKey, TVal> : Dictionary<TKey, TVal>, IXmlSerializable, ISerializable
{
        #region Constants
        private const string DictionaryNodeName = "Dictionary";
        private const string ItemNodeName = "Item";
        private const string KeyNodeName = "Key";
        private const string ValueNodeName = "Value";
        #endregion
        #region Constructors
        public SerializableDictionary()
        {
        }

        public SerializableDictionary(IDictionary<TKey, TVal> dictionary)
            : base(dictionary)
        {
        }

        public SerializableDictionary(IEqualityComparer<TKey> comparer)
            : base(comparer)
        {
        }

        public SerializableDictionary(int capacity)
            : base(capacity)
        {
        }

        public SerializableDictionary(IDictionary<TKey, TVal> dictionary, IEqualityComparer<TKey> comparer)
            : base(dictionary, comparer)
        {
        }

        public SerializableDictionary(int capacity, IEqualityComparer<TKey> comparer)
            : base(capacity, comparer)
        {
        }

        #endregion
        #region ISerializable Members

        protected SerializableDictionary(SerializationInfo info, StreamingContext context)
        {
            int itemCount = info.GetInt32("ItemCount");
            for (int i = 0; i < itemCount; i++)
            {
                KeyValuePair<TKey, TVal> kvp = (KeyValuePair<TKey, TVal>)info.GetValue(String.Format("Item{0}", i), typeof(KeyValuePair<TKey, TVal>));
                this.Add(kvp.Key, kvp.Value);
            }
        }

        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("ItemCount", this.Count);
            int itemIdx = 0;
            foreach (KeyValuePair<TKey, TVal> kvp in this)
            {
                info.AddValue(String.Format("Item{0}", itemIdx), kvp, typeof(KeyValuePair<TKey, TVal>));
                itemIdx++;
            }
        }

        #endregion
        #region IXmlSerializable Members

        void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer)
        {
            //writer.WriteStartElement(DictionaryNodeName);
            foreach (KeyValuePair<TKey, TVal> kvp in this)
            {
                writer.WriteStartElement(ItemNodeName);
                writer.WriteStartElement(KeyNodeName);
                KeySerializer.Serialize(writer, kvp.Key);
                writer.WriteEndElement();
                writer.WriteStartElement(ValueNodeName);
                ValueSerializer.Serialize(writer, kvp.Value);
                writer.WriteEndElement();
                writer.WriteEndElement();
            }
            //writer.WriteEndElement();
        }

        void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)
        {
            if (reader.IsEmptyElement)
            {
                return;
            }

            // Move past container
            if (!reader.Read())
            {
                throw new XmlException("Error in Deserialization of Dictionary");
            }

            //reader.ReadStartElement(DictionaryNodeName);
            while (reader.NodeType != XmlNodeType.EndElement)
            {
                reader.ReadStartElement(ItemNodeName);
                reader.ReadStartElement(KeyNodeName);
                TKey key = (TKey)KeySerializer.Deserialize(reader);
                reader.ReadEndElement();
                reader.ReadStartElement(ValueNodeName);
                TVal value = (TVal)ValueSerializer.Deserialize(reader);
                reader.ReadEndElement();
                reader.ReadEndElement();
                this.Add(key, value);
                reader.MoveToContent();
            }
            //reader.ReadEndElement();

            reader.ReadEndElement(); // Read End Element to close Read of containing node
        }

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

        #endregion
        #region Private Properties
        protected XmlSerializer ValueSerializer
        {
            get
            {
                if (valueSerializer == null)
                {
                    valueSerializer = new XmlSerializer(typeof(TVal));
                }
                return valueSerializer;
            }
        }

        private XmlSerializer KeySerializer
        {
            get
            {
                if (keySerializer == null)
                {
                    keySerializer = new XmlSerializer(typeof(TKey));
                }
                return keySerializer;
            }
        }
        #endregion
        #region Private Members
        private XmlSerializer keySerializer = null;
        private XmlSerializer valueSerializer = null;
        #endregion
}

The full C# file can be downloaded here: SerializableDictionary.cs

This class is both XML-serializable and binary-serializable. It is hard to find examples out there that do both.

9 comments on “C# Serializable Dictionary – a Working Example
  1. Jon, what won’t be remembered? The hash code? The key & value pairs? Also, are you using binary or XML serialization? And what types are you serializing? I know, a lot of questions, but the solution really differs depending on how you answer those questions.

  2. The license for this code is MIT.

    Copyright (c) Dacris Software Inc.

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the “Software”), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    THE SOFTWARE.

  3. Hi Dan,

    Great piece of code; only problem I have found is that if the key of the dictionary is another object that also needs to be serialized the binary serialization will not work. Actually is the de serialization step the one that fails. This is due to the order in which the framework de serialize the objects. The object in the key will not be de serialized until the dictionary has been fully de serialized.
    Trying to find a solution and I will post it if I find anything.

    Cheers

  4. As mentioned by Federico, there is a problem in deserialization because the internal state is not ready to add key/value pairs in the deserialization constructor. This is a dangerous pattern to use – you should never call virtual methods within a constructor.

    Second, you do not need to explicitly declare the ISerializable interface as this class does not add any new object variables that must be included in the binary serialization, so you can rely entirely on the parent class to properly handle binary serialization and deserialization as it already does that. If you do want to locally handle serialization/deserialization then you should delegate to the methods responsible for this in the base class and not role your own. In that case, you must also implement the method that restores the internal state used by Dictionary through the IDeserializationCallback interface. So it might look like this:

    public class SerializableDictionary : Dictionary, IXmlSerializable, ISerializable, IDeserializationCallback
    {

    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {
    base.GetObjectData(info, context);
    }

    void IDeserializationCallback.OnDeserialization(object sender)
    {
    base.OnDeserialization(sender);
    }

    }

    As I noted above, though, these methods are not needed unless you need to add some object variables (but none are used here other then then the XmlSerializer references). You can remove entirely the ISerializable interface reference as this is contained in the parent class.

    Additionally, those local XmlSerializer variables should be marked [NonSerializable] as they are not themselves serialized/deserialized during binary serialization.

  5. And how can I transfer this SerializableDictionary into my web service?

    I added this class to my web service project, created there simple function

    [WebMethod]
    public string HelloWorld(SerializableDictionary s)
    {
    return “Hello World M!”;
    }

    And when I try to call this method from client application, intellisense shows me this type as DataSet, not SerializableDictionary.

    What am I doing wrong?

Comments are closed.