为啥 .NET 中没有可序列化 XML 的字典?

Posted

技术标签:

【中文标题】为啥 .NET 中没有可序列化 XML 的字典?【英文标题】:Why isn't there an XML-serializable dictionary in .NET?为什么 .NET 中没有可序列化 XML 的字典? 【发布时间】:2010-11-10 14:37:33 【问题描述】:

我需要一个可序列化 XML 的字典。实际上,我现在有两个完全不同的程序需要一个。看到 .NET 没有一个,我感到相当惊讶。我在别处问了这个问题,得到了讽刺的回答。我不明白为什么这是一个愚蠢的问题。

鉴于各种 .NET 功能对 XML 序列化的依赖程度,有人可以启发我,为什么没有可序列化 XML 的字典。希望您还可以解释为什么有些人认为这是一个愚蠢的问题。我想我一定遗漏了一些基本的东西,我希望你能填补这些空白。

【问题讨论】:

问题不正确,因为它的因果错误。应该是“为什么XmlSerializer 不能序列化字典”?因为在 .NET 中进行 XML 序列化的方法有很多,而且大多数都可以很好地序列化字典(DataContractSerializerSoapFormatter ...)。 我猜您还没有检查过“XmlDictionaryWriter.CreateDictionaryWriter”... 或 .NET 中序列化字典的 100 种其他方法(其中一些是内置的)。 ...还有为什么你需要一本字典?我一直发现强类型对象工作得更好,为什么不直接用 [DataContract] 和 IExtensibleDataObject 实现一个类? 您认为 .NET 中的哪些现代特性依赖于 XML 序列化?配置文件不使用序列化,并且 ASMX Web 服务仅用于传统用途。 (从答案移到评论) 【参考方案1】:

关于 XML 序列化的问题在于,它不仅仅是创建字节流。它还涉及创建一个 XML 模式,该字节流将对其进行验证。 XML Schema 中没有表示字典的好方法。你能做的最好的事情就是证明有一个唯一的键。

您始终可以创建自己的包装器,例如 One Way to Serialize Dictionaries。

【讨论】:

我的两个案例是web服务和配置文件。那么,您是说 .NET Framework 人员受到 XML Schema 规范缺陷的限制?我在网上找到了一些东西,但使用内置类比确定其他人是否做得对要少得多。我会看看你建议的那个。 ASMX Web 服务现在被视为遗留技术。见johnwsaundersiii.spaces.live.com/blog/…。有一个完整的配置文件 API - 它不使用 XML 序列化。还有什么? 顺便说一句,“限制”是一个设计决定。正如您所说,它用于 Web 服务 - 但不仅仅是用于序列化和反序列化 - 它是生成 WSDL 一部分的模式的原因。这都是整体的一部分,而且必须协同工作。 我知道它们是遗留问题,但这并不意味着我将有时间学习 WCF。有人指出,软件不应该是镀金的,它应该能胜任。 ASMX 完成了这项工作。微软开发 .NET 的步伐令人兴奋和美妙,但与当前市场脱节:培训预算削减、削减,只做必须做的工作。当我们说“我们需要升级,因为 Microsoft 将不再支持技术 X”时,业务的非 IT 部分看起来有些怀疑。 (我知道这不仅仅是 MS,而且经常是 MS。)所以我现在坚持使用 ASMX。 您说“鉴于各种 .NET 功能对 XML 序列化的依赖程度”,您无法理解为什么没有。我说过.NET 的一些特性依赖于 XML Ser。您提到了 ASMX 和 Config。我说 ASMX 是遗留的,配置不使用 XML Ser。 “Legacy”旨在说明为什么他们不急于添加字典支持。另请参阅johnwsaundersiii.spaces.live.com/blog/…。【参考方案2】:

创建一个你自己的:-),只读特性是额外的,但如果你需要一个字符串以外的键,那么类需要一些修改......

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 中有空格,读取可能会进入无限循环。我修复了这个错误,但可能还有更多。【参考方案3】:

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

我同意它不在一个特别容易被发现的地方。

【讨论】:

这些类不是通用的可序列化字典。它们与 WCF 中序列化的实现有关。 我不明白评论。为什么它们不是通用的可序列化 xml 的字典? “System.Xml.XmlDictionary”或“System.Runtime.Serialization”的哪一部分表示非泛型?【参考方案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>

【讨论】:

【参考方案5】:

这是我的实现。

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
    


【讨论】:

您应该评论使用您的更详细的解决方案相对于本主题其他地方提出的解决方案的优势。目前尚不清楚为什么要在更简单的实现中使用它。【参考方案6】:

我知道这个问题之前已经回答过了,但是由于我有一个非常简洁的方法(代码)来使用 DataContractSerializer 类(由 WCF 使用,但可以而且应该在任何地方使用)进行 IDictionary 序列化,我忍不住贡献了​​它这里:

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! p>

我将流式传输到字符串是因为它对我来说更方便,虽然我可以向 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。

希望它可以帮助那里的人!

【讨论】:

效果很好!致其他开发人员:如果您还没有 System.Runtime.Serialization 的项目参考,则需要添加项目参考,但它在 .NET 4.0 客户端配置文件中可用。 它不适用于面向 Windows Phone 7.5(即 Silverlight 3)的 Windows Phone 8 SDK。 @Adarsha 根据 DataContractSerializer 文档,它支持以下平台:Windows 8、Windows Server 2012、Windows 7、Windows Vista SP2、Windows Server 2008(不支持服务器核心角色)、Windows Server 2008 R2( SP1 或更高版本支持服务器核心角色;不支持安腾)...它没有提到手机 SDK...Windows Phone 7 使用 .NET Compact Framework 3.7,因此没有 DataContractSerializer :-( 我已经更新了帖子相应地,这样人们就不会浪费时间弄清楚什么不起作用!感谢 Adarsha! 我不知道为什么我以前没有看到这个,但是 -1 用于使用 new XmlTextWriternew XmlTextReader 而不是 XmlReader.CreateXmlWriter.Create @JohnSaunders 为什么我已经知道我想要哪个 XmlReader 或 Writer 时要这样做。你看过 XmlWriter/Reader.Create 吗?调用它会有很大的损失,而且这个方法应该尽可能快,因为它可以用于序列化大量对象的紧密循环中(我承认如果性能是这里的问题,我会使用另一种序列化方法)。但无论如何,推荐的方式是使用 XmlWriter/Reader.Create,但由于我从一开始就在编程 .NET(版本 1),我想我习惯于用“旧”方式做一些事情。【参考方案7】:

一个通用的帮助器,可以快速地将 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

【讨论】:

【参考方案8】:

我知道这已经被做死了,但这是我的贡献。我从@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 泵送整个类层次结构即可。

【讨论】:

以上是关于为啥 .NET 中没有可序列化 XML 的字典?的主要内容,如果未能解决你的问题,请参考以下文章

51nod 1255 字典序最小的子序列

leetcode31 下一个排列 字典序

1081. 不同字符的最小子序列

面试:字符串字典序最大的子序列

[补题]找到原序列长度k的子序列中字典序最小的那个(单调栈)

为啥html,xml的特殊符号转义不用斜杠,而要用& quot ;这样的奇怪形式?