如何从不使用 XElement 的自定义 XML 序列化/反序列化为 `Dictionary<int, string>`?

Posted

技术标签:

【中文标题】如何从不使用 XElement 的自定义 XML 序列化/反序列化为 `Dictionary<int, string>`?【英文标题】:How to serialize/deserialize to `Dictionary<int, string>` from custom XML not using XElement? 【发布时间】:2012-09-15 06:32:36 【问题描述】:

有空的Dictionary&lt;int, string&gt; 如何用来自 XML 的键和值来填充它,比如

<items>
<item id='int_goes_here' value='string_goes_here'/>
</items>

并在不使用 XElement 的情况下将其序列化回 XML?

【问题讨论】:

解释为什么您不想要特定解决方案会很有帮助(即使 XElement 可能不是正确的方法)。 为什么不想使用 XElement?序列化/反序列化可以用 linq 在一行中完成。 【参考方案1】:

借助一个临时的item

public class item

    [XmlAttribute]
    public int id;
    [XmlAttribute]
    public string value;

示例字典:

Dictionary<int, string> dict = new Dictionary<int, string>()

    1,"one", 2,"two"
;

.

XmlSerializer serializer = new XmlSerializer(typeof(item[]), 
                                 new XmlRootAttribute()  ElementName = "items" );

序列化

serializer.Serialize(stream, 
              dict.Select(kv=>new item()id = kv.Key,value=kv.Value).ToArray() );

反序列化

var orgDict = ((item[])serializer.Deserialize(stream))
               .ToDictionary(i => i.id, i => i.value);

----------------------------------------------- -------------------------------------------

如果您改变主意,使用 XElement 可以做到这一点。

序列化

XElement xElem = new XElement(
                    "items",
                    dict.Select(x => new XElement("item",new XAttribute("id", x.Key),new XAttribute("value", x.Value)))
                 );
var xml = xElem.ToString(); //xElem.Save(...);

反序列化

XElement xElem2 = XElement.Parse(xml); //XElement.Load(...)
var newDict = xElem2.Descendants("item")
                    .ToDictionary(x => (int)x.Attribute("id"), x => (string)x.Attribute("value"));

【讨论】:

我在循环中使用你的代码。而且我有内存泄漏。我添加了 XmlSerializer.dispose()。但是没有变化……正常吗? XmlSerializer 的一些构造函数泄漏内存(临时生成的程序集) - 你应该缓存它们参见support.microsoft.com/en-us/help/886385/… @L.B 是否可以扩展它以序列化 Dictionary> ?我做不到,谢谢。【参考方案2】:

Paul Welter 的ASP.NET blog 有一个可序列化的字典。但它不使用属性。我会在代码下面解释原因。

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

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

            writer.WriteEndElement();
        
    
    #endregion

首先,此代码有一个问题。假设您从另一个来源阅读了一本字典:

<dictionary>
  <item>
    <key>
      <string>key1</string>
    </key>
    <value>
      <string>value1</string>
    </value>
  </item>
  <item>
    <key>
      <string>key1</string>
    </key>
    <value>
      <string>value2</string>
    </value>
  </item>
</dictionary>

这将在反序列化时引发异常,因为字典只能有一个键。


您必须在序列化字典中使用 XElement 的原因是字典未定义为 Dictionary&lt;String,String&gt;,字典为 Dictionary&lt;TKey,TValue&gt;

要看到问题,问问你自己:假设我们有一个TValue,它序列化为使用元素的东西,它把自己描述为 XML(假设一个字典字典 Dictionary&lt;int,Dictionary&lt;int,string&gt;&gt;(这种模式并不罕见) ,它是一个查找表)),您的 Attribute only 版本将如何完全表示一个属性内的字典?

【讨论】:

您通过调用“ReadXml”方法进行反序列化。 您想要WriteStartElement("item")ReadStartElement("item") 等等 foreach 循环中,对吧? 能否请您举例说明如何使用这个类?【参考方案3】:

字典在 C# 中默认是不可序列化的,我不知道为什么,但这似乎是一种设计选择。

现在,我建议使用Json.NET 将其转换为 JSON 并从那里转换为字典(反之亦然)。除非您真的需要 XML,否则我建议您完全使用 JSON。

【讨论】:

据我了解,他想将 XML 反序列化为字典。如果 Dictionary 是可序列化的,您可以使用 XmlSerializer 等内置类来完成任务,但正如我所说,它不是。【参考方案4】:

基于 L.B. 的回答。

用法:

var serializer = new DictionarySerializer<string, string>();
serializer.Serialize("dictionary.xml", _dictionary);
_dictionary = _titleDictSerializer.Deserialize("dictionary.xml");

通用类:

public class DictionarySerializer<TKey, TValue>

    [XmlType(TypeName = "Item")]
    public class Item
    
        [XmlAttribute("key")]
        public TKey Key;
        [XmlAttribute("value")]
        public TValue Value;
    

    private XmlSerializer _serializer = new XmlSerializer(typeof(Item[]), new XmlRootAttribute("Dictionary"));

    public Dictionary<TKey, TValue> Deserialize(string filename)
    
        using (FileStream stream = new FileStream(filename, FileMode.Open))
        using (XmlReader reader = XmlReader.Create(stream))
        
            return ((Item[])_serializer.Deserialize(reader)).ToDictionary(p => p.Key, p => p.Value);
        
    

    public void Serialize(string filename, Dictionary<TKey, TValue> dictionary)
    
        using (var writer = new StreamWriter(filename))
        
            _serializer.Serialize(writer, dictionary.Select(p => new Item()  Key = p.Key, Value = p.Value ).ToArray());
        
    

【讨论】:

【参考方案5】:

我有一个结构 KeyValuePairSerializable:

[Serializable]
public struct KeyValuePairSerializable<K, V>

    public KeyValuePairSerializable(KeyValuePair<K, V> pair)
    
        Key = pair.Key;
        Value = pair.Value;
    

    [XmlAttribute]
    public K Key  get; set; 

    [XmlText]
    public V Value  get; set; 

    public override string ToString()
    
        return "[" + StringHelper.ToString(Key, "") + ", " + StringHelper.ToString(Value, "") + "]";
    

那么,Dictionary 属性的 XML 序列化是:

[XmlIgnore]
public Dictionary<string, string> Parameters  get; set; 

[XmlArray("Parameters")]
[XmlArrayItem("Pair")]
[DebuggerBrowsable(DebuggerBrowsableState.Never)] // not necessary
public KeyValuePairSerializable<string, string>[] ParametersXml

    get 
     
        return Parameters?.Select(p => new KeyValuePairSerializable<string, string>(p)).ToArray(); 
    
    set
    
        Parameters = value?.ToDictionary(i => i.Key, i => i.Value);
    

只有属性必须是数组,而不是列表。

【讨论】:

【参考方案6】:

您可以使用ExtendedXmlSerializer。 如果你有课:

public class TestClass

    public Dictionary<int, string> Dictionary  get; set; 

并创建此类的实例:

var obj = new TestClass

    Dictionary = new Dictionary<int, string>
    
        1, "First",
        2, "Second",
        3, "Other",
    
;

您可以使用 ExtendedXmlSerializer 序列化此对象:

var serializer = new ConfigurationContainer()
    .UseOptimizedNamespaces() //If you want to have all namespaces in root element
    .Create();

var xml = serializer.Serialize(
    new XmlWriterSettings  Indent = true , //If you want to formated xml
    obj);

输出 xml 将如下所示:

<?xml version="1.0" encoding="utf-8"?>
<TestClass xmlns:sys="https://extendedxmlserializer.github.io/system" xmlns:exs="https://extendedxmlserializer.github.io/v2" xmlns="clr-namespace:ExtendedXmlSerializer.Samples;assembly=ExtendedXmlSerializer.Samples">
  <Dictionary>
    <sys:Item>
      <Key>1</Key>
      <Value>First</Value>
    </sys:Item>
    <sys:Item>
      <Key>2</Key>
      <Value>Second</Value>
    </sys:Item>
    <sys:Item>
      <Key>3</Key>
      <Value>Other</Value>
    </sys:Item>
  </Dictionary>
</TestClass>

您可以从nuget 安装 ExtendedXmlSerializer 或运行以下命令:

Install-Package ExtendedXmlSerializer

【讨论】:

【参考方案7】:

编写一个类 A,它包含一个类 B 的数组。类 B 应该有一个 id 属性和一个 value 属性。将 xml 反序列化为 A 类。将 A 中的数组转换为所需的字典。

要序列化字典,将其转换为 A 类的实例,然后序列化...

【讨论】:

【参考方案8】:

KeyedCollection 像字典一样工作并且是可序列化的。

首先创建一个包含键和值的类:

/// <summary>
/// simple class
/// </summary>
/// <remarks></remarks>
[Serializable()]
public class cCulture

    /// <summary>
    /// culture
    /// </summary>
    public string culture;

    /// <summary>
    /// word list
    /// </summary>
    public List<string> list;

    /// <summary>
    /// status
    /// </summary>
    public string status;

然后创建一个 KeyedCollection 类型的类,并将你的类的一个属性定义为键。

/// <summary>
/// keyed collection.
/// </summary>
/// <remarks></remarks>
[Serializable()]
public class cCultures : System.Collections.ObjectModel.KeyedCollection<string, cCulture>

    protected override string GetKeyForItem(cCulture item)
    
        return item.culture;
    

对序列化这种类型的数据很有用。

【讨论】:

【参考方案9】:

我将可序列化的类用于不同模块之间的 WCF 通信。 下面是一个可序列化类的示例,它也用作 DataContract。 我的方法是使用 LINQ 的强大功能将 Dictionary 转换为 KeyValuePair 的开箱即用的可序列化 List:

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

    namespace MyFirm.Common.Data
    
        [DataContract]
        [Serializable]
        public class SerializableClassX
        
            // since the Dictionary<> class is not serializable,
            // we convert it to the List<KeyValuePair<>>
            [XmlIgnore]
            public Dictionary<string, int> DictionaryX 
            
                get
                
                    return  SerializableList == null ? 
                            null :
                            SerializableList.ToDictionary(item => item.Key, item => item.Value);
                

                set
                
                    SerializableList =  value == null ?
                                        null :
                                        value.ToList();
                
            

            [DataMember]
            [XmlArray("SerializableList")]
            [XmlArrayItem("Pair")]
            public List<KeyValuePair<string, int>> SerializableList  get; set; 
        
    

用法很简单 - 我为我的数据对象的字典字段 - DictionaryX 分配了一个字典。通过将分配的字典转换为 KeyValuePair 的可序列化 List,在 SerializableClassX 内部支持序列化:

    // create my data object
    SerializableClassX SerializableObj = new SerializableClassX(param);

    // this will call the DictionaryX.set and convert the '
    // new Dictionary into SerializableList
    SerializableObj.DictionaryX = new Dictionary<string, int>
    
        "Key1", 1,
        "Key2", 2,
    ;

【讨论】:

【参考方案10】:

Sharpeserializer(开源)有一个简单的方法:

http://www.sharpserializer.com/

可以直接序列化/反序列化字典。

不需要用任何属性标记你的对象,也不需要在 Serialize 方法中给出对象类型(参见here)。

通过 nuget 安装:Install-package sharpserializer

那么就很简单了:

Hello World(来自官网):

// create fake obj
var obj = createFakeObject();

// create instance of sharpSerializer
// with standard constructor it serializes to xml
var serializer = new SharpSerializer();

// serialize
serializer.Serialize(obj, "test.xml");

// deserialize
var obj2 = serializer.Deserialize("test.xml");

【讨论】:

以上是关于如何从不使用 XElement 的自定义 XML 序列化/反序列化为 `Dictionary<int, string>`?的主要内容,如果未能解决你的问题,请参考以下文章

使用 XElement 保存 XML 文件时,文件中的对齐方式也会发生变化,如何避免?

如何使用 XElement.Parse 解析 XML 而无需查看/将实体更改为字符?

如何将 Dictionary<XElement,XElement> 转换为 xml 文件?

如何在c#中使用xelement获取xml节点值

如何将带有 xml 后代的 XElement 导入 SQL Server

使用 XElement 解析 XML