views:

4465

answers:

4

I need an XML-serializable dictionary. Actually, I now have two quite different programs that need one. I was rather surprised to see that .NET doesn't have one. I asked the question elsewhere and got sarcastic responses. I don't understand why it's a stupid question.

Can someone enlighten me, given how dependent various .NET features are on XML serialization, why there isn't an XML-serializable dictionary. Hopefully, you can also explain why some people consider that a daft question. I guess I must be missing something fundamental and I'm hoping you'll be able to fill in the gaps.

+4  A: 

Which features in .NET do you think are dependent on XML Serialization?

The thing about XML Serialization is that it's not just about creating a stream of bytes. It's also about creating an XML Schema that this stream of bytes would validate against. There's no good way in XML Schema to represent a dictionary. The best you could do is to show that there's a unique key.

You can always create your own wrapper, for instance One Way to Serialize Dictionaries.

John Saunders
My two cases are web services and configuration files. So, you're saying that the .NET Framework guys were limited by a deficiency in the XML Schema specification?I have found stuff online but using a built-in class in a lot less work than deciding if someone else has done it right. I'll have a look at the one you suggested.
serialhobbyist
ASMX web services are now considered legacy technology. See http://johnwsaundersiii.spaces.live.com/blog/cns!600A2BE4A82EA0A6!860.entry. There's an entire API for configuration files - it doesn't use XML Serialization. Anything else?
John Saunders
BTW, the "limitation" is a design decision. As you say, it was used for web services - but not just to serialize and deserialize - it's what produced the schemas that are part of the WSDL. It's all part of a whole, and it all has to work together.
John Saunders
I know they're legacy but that doesn't mean that I am going to be given the time to learn WCF. Someone noted that software shouldn't be gold-plated, it should do the job. ASMX does the job. The pace of Microsoft's development of .NET is exciting and wonderful but out of touch with the current market: training budgets slashed, cutting back, only doing work that MUST be done. The non-IT parts of the business look askance when we say "We need to upgrade because Microsoft won't be supporting technology X any more". (I know it's not just MS but it is OFTEN MS.) So I'm stuck with ASMX for now.
serialhobbyist
You said that "given how dependent various .NET features are on XML serialization" you couldn't understand why there wasn't one. I said there are few features of .NET dependent on XML Ser. You mentioned ASMX and Config. I said ASMX is legacy and config doesn't use XML Ser. "Legacy" was meant to show why they'd be in no hurry to add dictionary support. Also, see http://johnwsaundersiii.spaces.live.com/blog/cns!600A2BE4A82EA0A6!860.entry.
John Saunders
I concede the point on config - it was a while ago - I've clearly mis-remembered the problem. I've just found a series of articles on CodeProject about the Config API and I'm looking forward to getting my teeth into them. Your original reply answered my question. Thank you. I was just proving an alternative view to your 'legacy' point. I love .NET. I just wish MS would slow down.
serialhobbyist
But, life doesn't slow down. WCF was introduced almost three years ago, with the clear intention of replacing ASMX, WSE and Remoting. Microsoft is only now beginning to publicly acknowledge that, and I think it's past time.
John Saunders
I suppose not. I'm surprised it's been 3 years: doesn't seem like it. I teach myself a lot in my own time but I've always been put off WCF by what might just be my perception that it's complicated and a lot more work to set up than ASMX. It'd be nice if MS would divert some of their .NET energy to increasing the coverage of .NET over Win32 so I didn't have to Interop or P/Invoke so much. But in the same way that I have to be able to justify the non-productive periods whilst I'm learning new stuff, they have to be able to sell new versions of VS, don't they?
serialhobbyist
Take 5 minutes when you get a chance and see http://johnwsaundersiii.spaces.live.com/blog/cns!600A2BE4A82EA0A6!790.entry. It's got lots of pictures, for those who, unlike you, don't get the concepts of web services. But it happens to include a quick run-through (or run-past) of a trivial WCF service and client. Also, http://msdn.microsoft.com/en-us/netframework/dd939784.aspx for good "lunch time" video viewing.
John Saunders
@John, how do you know that the limitation is a design decision? It's the first time I hear anyone say that.
Pavel Minaev
@Pavel: "limitation" was in the context of the comment from the OP: "limited by a deficiency in the XML Schema specification". My understanding is that the design decision was to produce XML that can be described by XML Schema.
John Saunders
+3  A: 

Create one of your own :-), the readonly feature is bonus but if you need a key other than a string then the class needs some modifications...

namespace MyNameSpace
{
    [XmlRoot("SerializableDictionary")]
    public class SerializableDictionary : Dictionary<String, Object>, IXmlSerializable
    {
        internal Boolean _ReadOnly = false;
        public Boolean ReadOnly
        {
            get
            {
                return this._ReadOnly;
            }

            set
            {
                this.CheckReadOnly();
                this._ReadOnly = value;
            }
        }

        public new Object this[String key]
        {
            get
            {
                Object value;

                return this.TryGetValue(key, out value) ? value : null;
            }

            set
            {
                this.CheckReadOnly();

                if(value != null)
                {
                    base[key] = value;
                }
                else
                {
                    this.Remove(key);
                }               
            }
        }

        internal void CheckReadOnly()
        {
            if(this._ReadOnly)
            {
                throw new Exception("Collection is read only");
            }
        }

        public new void Clear()
        {
            this.CheckReadOnly();

            base.Clear();
        }

        public new void Add(String key, Object value)
        {
            this.CheckReadOnly();

            base.Add(key, value);
        }

        public new void Remove(String key)
        {
            this.CheckReadOnly();

            base.Remove(key);
        }

        public XmlSchema GetSchema()
        {
            return null;
        }

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

            reader.Read();

            if(wasEmpty)
            {
                return;
            }

            while(reader.NodeType != XmlNodeType.EndElement)
            {
                if(reader.Name == "Item")
                {
                    String key = reader.GetAttribute("Key");
                    Type type = Type.GetType(reader.GetAttribute("TypeName"));

                    reader.Read();
                    if(type != null)
                    {
                        this.Add(key, new XmlSerializer(type).Deserialize(reader));
                    }
                    else
                    {
                        reader.Skip();
                    }
                    reader.ReadEndElement();

                    reader.MoveToContent();
                }
                else
                {
                    reader.ReadToFollowing("Item");
                }

            reader.ReadEndElement();
        }

        public void WriteXml(XmlWriter writer)
        {
            foreach(KeyValuePair<String, Object> item in this)
            {
                writer.WriteStartElement("Item");
                writer.WriteAttributeString("Key", item.Key);
                writer.WriteAttributeString("TypeName", item.Value.GetType().AssemblyQualifiedName);

                new XmlSerializer(item.Value.GetType()).Serialize(writer, item.Value);

                writer.WriteEndElement();
            }
        }

    }
}
bang
There was a bug in this code -- if there was whitespace in the xml the reading could enter an infinite loop. I fixed this bug but there may be more.
Luke
+5  A: 

They added one in .NET 3.0. If you can, add a reference to System.Runtime.Serialization and look for System.Xml.XmlDictionary, System.Xml.XmlDictionaryReader, and System.Xml.XmlDictionaryWriter.

I would agree that it is not in a particularly discoverable place.

Joe Chung
These classes are not general-purpose serializable dictionaries. They're related to the implementation of serialization in WCF.
John Saunders
+2  A: 

Use the DataContractSerializer! See the sample below.

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

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            a.Value = 1;

            B b = new B();
            b.Value = "SomeValue";

            Dictionary<A, B> d = new Dictionary<A,B>();
            d.Add(a, b);
            DataContractSerializer dcs = new DataContractSerializer(typeof(Dictionary<A, B>));
            StringBuilder sb = new StringBuilder();
            using (XmlWriter xw = XmlWriter.Create(sb))
            {
                dcs.WriteObject(xw, d);
            }
            string xml = sb.ToString();
        }
    }

    public class A
    {
        public int Value
        {
            get;
            set;
        }
    }

    public class B
    {
        public string Value
        {
            get;
            set;
        }
    }
}

The above code produces the following xml:

<?xml version="1.0" encoding="utf-16"?>
<ArrayOfKeyValueOfABHtQdUIlS xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays"&gt;
    <KeyValueOfABHtQdUIlS>
        <Key xmlns:d3p1="http://schemas.datacontract.org/2004/07/ConsoleApplication1"&gt;
            <d3p1:Value>1</d3p1:Value>
        </Key>
        <Value xmlns:d3p1="http://schemas.datacontract.org/2004/07/ConsoleApplication1"&gt;
            <d3p1:Value>SomeValue</d3p1:Value>
        </Value>
    </KeyValueOfABHtQdUIlS>
</ArrayOfKeyValueOfABHtQdUIlS>
Bram
<?xml version="1.0" encoding="utf-16" ?> <ArrayOfKeyValueOfABHtQdUIlS xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays"> <KeyValueOfABHtQdUIlS> <Key xmlns:d3p1="http://schemas.datacontract.org/2004/07/ConsoleApplication1"> <d3p1:Value>0</d3p1:Value> </Key> <Value xmlns:d3p1="http://schemas.datacontract.org/2004/07/ConsoleApplication1"> <d3p1:Value i:nil="true" /> </Value> </KeyValueOfABHtQdUIlS></ArrayOfKeyValueOfABHtQdUIlS>
Bram
@Bram: what's that? You should edit your answer if you want to add more information.
John Saunders