为什么.NET没有可XML序列化的字典?

73

我需要一个可XML序列化的字典。实际上,我现在有两个需要它的程序。看到.NET没有这个功能,我感到相当惊讶。

有人可以告诉我,考虑到各种.NET功能对XML序列化的依赖性,为什么没有一个可XML序列化的字典呢?


5
这个问题是不正确的,因为它把因果关系搞错了。应该是,“为什么XmlSerializer不能序列化字典”?因为在.NET中有许多方法可以进行XML序列化,其中大部分都可以很好地序列化字典(如DataContractSerializerSoapFormatter等)。 - Pavel Minaev
我猜你还没有研究过"XmlDictionaryWriter.CreateDictionaryWriter",或者其他100种在.NET中序列化字典的方法(其中一些是内置的)。另外,为什么你需要一个字典呢?我总是发现强类型对象更好用,为什么不实现一个带有[DataContract]和IExtensibleDataObject的类呢? - BrainSlugs83
你认为.NET中哪些现代特性依赖于XML序列化?配置文件不使用序列化,而ASMX Web服务仅用于遗留用途。(从答案移至评论) - John Saunders
8个回答

53

我知道这个问题之前已经有答案了,但是我有一种非常简洁的方法(代码)可以使用DataContractSerializer类进行IDictionary序列化(这个类被WCF使用,但是它也可以和应该在其他地方使用),我无法抵制在这里做出贡献:

public static class SerializationExtensions
{
    public static string Serialize<T>(this T obj)
    {
        var serializer = new DataContractSerializer(obj.GetType());
        using (var writer = new StringWriter())
        using (var stm = new XmlTextWriter(writer))
        {
            serializer.WriteObject(stm, obj);
            return writer.ToString();
        }
    }
    public static T Deserialize<T>(this string serialized)
    {
        var serializer = new DataContractSerializer(typeof(T));
        using (var reader = new StringReader(serialized))
        using (var stm = new XmlTextReader(reader))
        {
            return (T)serializer.ReadObject(stm);
        }
    }
}

这在.NET 4中完美运行,并且应该也可以在.NET 3.5中运行,尽管我还没有测试过。
更新:它不适用于.NET Compact Framework(甚至不适用于Windows Phone 7的NETCF 3.7),因为DataContractSerializer不受支持!
我将流式传输到字符串中是因为对我来说更方便,尽管我本可以引入较低级别的序列化到Stream中,然后使用它来序列化到字符串,但我倾向于只在需要时进行泛化(就像过早进行优化一样是邪恶的,所以是过早的泛化...)
使用非常简单:
// dictionary to serialize to string
Dictionary<string, object> myDict = new Dictionary<string, object>();
// add items to the dictionary...
myDict.Add(...);
// serialization is straight-forward
string serialized = myDict.Serialize();
...
// deserialization is just as simple
Dictionary<string, object> myDictCopy = 
    serialized.Deserialize<Dictionary<string,object>>();

myDictCopy将是myDict的逐字复制。

您还会注意到,提供的通用方法能够序列化任何类型(据我所知),因为它不仅限于IDictionary接口,实际上可以是任何泛型类型T。

希望它能帮助到某个人!


5
非常好用!对其他开发者来说:如果你还没有添加System.Runtime.Serialization项目引用,那么你需要添加一个,但是它已经可以在.NET 4.0客户端框架中使用。 - MCattle
我不知道为什么之前没看到,但是使用XmlReader.CreateXmlWriter.Create而不是new XmlTextWriternew XmlTextReader扣1分。 - John Saunders
1
@JohnSaunders 为什么我要这样做,当我已经知道我想要哪个XmlReader或Writer。你看过XmlWriter/Reader.Create吗?调用它会有很大的惩罚,而且这个方法应该尽可能快,因为它可以在紧密的循环中用于序列化大量对象(虽然如果性能是问题,我会使用另一种序列化方法)。但无论如何,推荐的方法是使用XmlWriter/Reader.Create,但由于我从一开始就在编写.NET(版本1),所以我想我习惯了以“旧”的方式做一些事情。 - Loudenvier
你不应该使用new XmlTextReader()new XmlTextWriter(),自.NET 2.0起它们已被弃用。请改用XmlReader.Create()XmlWriter.Create() - John Saunders
@Loudenvier:你需要XmlTextWriter的哪个特性?你的代码同样可以使用XmlWriter.Create,而且应该这样做。就目前而言,它是一个糟糕的例子,值得被踩。 - John Saunders
显示剩余6条评论

15
关于XML序列化的问题在于它不仅涉及到创建一个字节流,还包括创建一个符合该字节流验证的XML模式。在XML模式中,没有很好的方法来表示一个字典。最好的方法是表明有一个唯一的键。你可以总是创建自己的包装器,例如序列化字典的一种方法

我的两个案例是Web服务和配置文件。所以,你的意思是说.NET Framework的开发人员受到XML Schema规范的缺陷的限制?我在网上找到了一些东西,但使用内置类比决定是否有人做对了要容易得多。我会看看你建议的那个。 - serialhobbyist
ASMX网服务现在被视为旧技术。请参见http://johnwsaundersiii.spaces.live.com/blog/cns!600A2BE4A82EA0A6!860.entry。有一个完整的配置文件API,它不使用XML序列化。还有其他的吗? - John Saunders
我承认在配置方面有些问题 - 那是一段时间以前了 - 我显然记错了问题。我刚刚在CodeProject上找到了一系列关于Config API的文章,我很期待深入研究它们。你原来的回复回答了我的问题。谢谢。我只是想证明你关于“遗留系统”的观点还有另一种看法。我喜欢.NET。我只希望微软能放慢速度。 - serialhobbyist
我想不是这样的。我很惊讶已经过去了3年,感觉好像没有那么长时间。虽然我在自己的时间里学到了很多东西,但我一直被WCF吓到,因为我的印象是它很复杂,设置起来比ASMX要花更多的功夫。如果微软能将一些.NET的精力投入到扩大.NET在Win32上的覆盖范围上,那就太好了,这样我就不必那么频繁地使用Interop或P/Invoke了。但是就像我必须能够证明在学习新知识的非生产性期间我是有价值的一样,他们也必须能够销售新版本的VS,对吧? - serialhobbyist
有机会的话,花5分钟看看http://johnwsaundersiii.spaces.live.com/blog/cns!600A2BE4A82EA0A6!790.entry。对于那些不像你一样理解Web服务概念的人来说,它有很多图片。但它恰好包括一个微不足道的WCF服务和客户端的快速演示。此外,http://msdn.microsoft.com/en-us/netframework/dd939784.aspx是一个不错的“午餐时间”视频观看网站。 - John Saunders
显示剩余7条评论

13

他们在.NET 3.0中添加了一个。如果可以的话,请添加对System.Runtime.Serialization的引用,并查找System.Xml.XmlDictionary、System.Xml.XmlDictionaryReader和System.Xml.XmlDictionaryWriter。

我同意它不是特别易于发现的地方。


4
这些类不是通用的可序列化字典,它们与 WCF 中序列化实现有关。 - John Saunders
我不理解这个评论。为什么它们不是通用的可序列化XML字典?“System.Xml.XmlDictionary”或“System.Runtime.Serialization”的哪一部分表明了非泛型性? - Robin Davies

4
创建自己的类,:-) 只读特性是额外奖励,但如果你需要一个非字符串类型的键,则需要对该类进行一些修改...
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();
            }
        }

    }
}

这段代码中有一个错误——如果xml中有空格,读取过程可能会进入无限循环。我已经修复了这个错误,但可能还有其他错误。 - Luke

4
使用DataContractSerializer!请参见以下示例。
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;
        }
    }
}

上述代码生成以下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">
    <KeyValueOfABHtQdUIlS>
        <Key xmlns:d3p1="http://schemas.datacontract.org/2004/07/ConsoleApplication1">
            <d3p1:Value>1</d3p1:Value>
        </Key>
        <Value xmlns:d3p1="http://schemas.datacontract.org/2004/07/ConsoleApplication1">
            <d3p1:Value>SomeValue</d3p1:Value>
        </Value>
    </KeyValueOfABHtQdUIlS>
</ArrayOfKeyValueOfABHtQdUIlS>

2

一个通用的帮助程序,可以在不使用继承的情况下快速向任何(现有的)字典添加IXmlSerializable:

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

namespace GameSpace {

    public class XmlSerializerForDictionary {

        public struct Pair<TKey,TValue> {

            public TKey Key;
            public TValue Value;

            public Pair(KeyValuePair<TKey,TValue> pair) {
                Key = pair.Key;
                Value = pair.Value;
            }//method

        }//struct

        public static void WriteXml<TKey,TValue>(XmlWriter writer, IDictionary<TKey,TValue> dict) {

            var list = new List<Pair<TKey,TValue>>(dict.Count);

            foreach (var pair in dict) {
                list.Add(new Pair<TKey,TValue>(pair));
            }//foreach

            var serializer = new XmlSerializer(list.GetType());
            serializer.Serialize(writer, list);

        }//method

        public static void ReadXml<TKey, TValue>(XmlReader reader, IDictionary<TKey, TValue> dict) {

            reader.Read();

            var serializer = new XmlSerializer(typeof(List<Pair<TKey,TValue>>));
            var list = (List<Pair<TKey,TValue>>)serializer.Deserialize(reader);

            foreach (var pair in list) {
                dict.Add(pair.Key, pair.Value);
            }//foreach

            reader.Read();

        }//method

    }//class

}//namespace

还有一个方便的可序列化通用字典:

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

namespace GameSpace {

    public class SerializableDictionary<TKey,TValue> : Dictionary<TKey,TValue>, IXmlSerializable {

        public virtual void WriteXml(XmlWriter writer) {
            XmlSerializerForDictionary.WriteXml(writer, this);
        }//method

        public virtual void ReadXml(XmlReader reader) {
            XmlSerializerForDictionary.ReadXml(reader, this);
        }//method

        public virtual XmlSchema GetSchema() {
            return null;
        }//method

    }//class

}//namespace

1

这是我的实现。

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

namespace Rubik.Staging
{    
    [XmlSchemaProvider("GetInternalSchema")]
    public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable
    {
        #region IXmlSerializable Members

        private const string ns = "http://www.rubik.com.tr/staging";

        public static XmlQualifiedName GetInternalSchema(XmlSchemaSet xs)
        {
            bool keyIsSimple = (typeof(TKey).IsPrimitive || typeof(TKey) == typeof(string));
            bool valueIsSimple = (typeof(TValue).IsPrimitive || typeof(TValue) == typeof(string));

            XmlSchemas schemas = new XmlSchemas();

            XmlReflectionImporter importer = new XmlReflectionImporter(ns);
            importer.IncludeType(typeof(TKey));            
            importer.IncludeType(typeof(TValue));            

            XmlTypeMapping keyMapping = importer.ImportTypeMapping(typeof(TKey));            
            XmlTypeMapping valueMapping = importer.ImportTypeMapping(typeof(TValue));          

            XmlSchemaExporter exporter = new XmlSchemaExporter(schemas); 

            if(!keyIsSimple)
                exporter.ExportTypeMapping(keyMapping);
            if(!valueIsSimple)
                exporter.ExportTypeMapping(valueMapping);

            XmlSchema schema = (schemas.Count == 0 ? new XmlSchema() : schemas[0]);

            schema.TargetNamespace = ns;          
            XmlSchemaComplexType type = new XmlSchemaComplexType();
            type.Name = "DictionaryOf" + keyMapping.XsdTypeName + "And" + valueMapping.XsdTypeName;
            XmlSchemaSequence sequence = new XmlSchemaSequence();
            XmlSchemaElement item = new XmlSchemaElement();
            item.Name = "Item";

            XmlSchemaComplexType itemType = new XmlSchemaComplexType();            
            XmlSchemaSequence itemSequence = new XmlSchemaSequence();

            XmlSchemaElement keyElement = new XmlSchemaElement();

            keyElement.Name = "Key";
            keyElement.MaxOccurs = 1;
            keyElement.MinOccurs = 1;

            XmlSchemaComplexType keyType = new XmlSchemaComplexType();
            XmlSchemaSequence keySequence = new XmlSchemaSequence();
            XmlSchemaElement keyValueElement = new XmlSchemaElement();
            keyValueElement.Name = keyMapping.ElementName;
            keyValueElement.SchemaTypeName = new XmlQualifiedName(keyMapping.XsdTypeName, keyMapping.XsdTypeNamespace);
            keyValueElement.MinOccurs = 1;
            keyValueElement.MaxOccurs = 1;
            keySequence.Items.Add(keyValueElement);
            keyType.Particle = keySequence;
            keyElement.SchemaType = keyType;
            itemSequence.Items.Add(keyElement);


            XmlSchemaElement valueElement = new XmlSchemaElement();

            valueElement.Name = "Value";
            valueElement.MaxOccurs = 1;
            valueElement.MinOccurs = 1;

            XmlSchemaComplexType valueType = new XmlSchemaComplexType();
            XmlSchemaSequence valueSequence = new XmlSchemaSequence();
            XmlSchemaElement valueValueElement = new XmlSchemaElement();
            valueValueElement.Name = valueMapping.ElementName;
            valueValueElement.SchemaTypeName = new XmlQualifiedName(valueMapping.XsdTypeName, valueMapping.XsdTypeNamespace);
            valueValueElement.MinOccurs = 1;
            valueValueElement.MaxOccurs = 1;
            valueSequence.Items.Add(valueValueElement);
            valueType.Particle = valueSequence;
            valueElement.SchemaType = valueType;
            itemSequence.Items.Add(valueElement);
            itemType.Particle = itemSequence;
            item.SchemaType = itemType;            
            sequence.Items.Add(item);
            type.Particle = sequence;
            schema.Items.Add(type);

            xs.XmlResolver = new XmlUrlResolver();
            xs.Add(schema);

            return new XmlQualifiedName(type.Name, ns);
        }





        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];
                valueSerializer.Serialize(writer, value);
                writer.WriteEndElement();

                writer.WriteEndElement();
            }
        }

        #endregion

        #region IXmlSerializable Members

        public XmlSchema GetSchema()
        {
            return null;
        }

        #endregion
    }

}

4
你应该评论一下使用你更冗长的解决方案相比于本主题中别人提出的解决方案的优点。目前还不清楚为什么应该使用它而不是更简单的实现方式。 - Renaud Bompuis

1
我知道这个问题已经被反复讨论了,但是这是我的贡献。我从@Loudenvier和@Jack的解决方案中提取了好的部分,并编写了自己的可序列化(抱歉,我是英国人)字典类。
public class SerialisableDictionary<T1, T2> : Dictionary<T1, T2>, IXmlSerializable
{
    private static DataContractSerializer serializer =
        new DataContractSerializer(typeof(Dictionary<T1, T2>));

    public void WriteXml(XmlWriter writer)
    {
        serializer.WriteObject(writer, this);
    }

    public void ReadXml(XmlReader reader)
    {
        Dictionary<T1, T2> deserialised =
            (Dictionary<T1, T2>)serializer.ReadObject(reader);

        foreach(KeyValuePair<T1, T2> kvp in deserialised)
        {
            Add(kvp.Key, kvp.Value);
        }
    }

    public XmlSchema GetSchema()
    {
        return null;
    }
}

我喜欢这种方法,因为你不需要显式地序列化或反序列化任何内容,只需将整个类层次结构通过XmlSerializer传输即可完成。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接