如何对字典进行XML序列化

24

我已经能够以这种方式序列化一个IEnumerable:

[XmlArray("TRANSACTIONS")]
[XmlArrayItem("TRANSACTION", typeof(Record))]
public IEnumerable<BudgetRecord> Records
{
    get 
    {
        foreach(Record br in _budget)
        {
            yield return br;
        }
    }
}

然而,我意识到现在我需要一个包含Dictionary<string, RecordCollection>的字典(RecordCollection实现了IEnumerable)。

我该如何实现?


我听说在.NET 4.0中,字典是可序列化的。 - kbrimington
如何使用自定义元素名称和属性来完成它? - Burnzy
请查看序列化字典的一种方法 - John Saunders
6个回答

17

请查看以下博客文章:

以及这篇(不是英文的,但代码很有用):


代码样例来自:http://web.archive.org/web/20100703052446/http://blogs.msdn.com/b/psheill/archive/2005/04/09/406823.aspx

using System.Collections.Generic;
using System.Collections;
using System.IO;
using System.Xml.Serialization;
using System.Xml;
using System;
public static void Serialize(TextWriter writer, IDictionary dictionary)
{
    List<Entry> entries = new List<Entry>(dictionary.Count);
    foreach (object key in dictionary.Keys)
    {
        entries.Add(new Entry(key, dictionary[key]));
    }
    XmlSerializer serializer = new XmlSerializer(typeof(List<Entry>));
    serializer.Serialize(writer, entries);
}
public static void Deserialize(TextReader reader, IDictionary dictionary)
{
    dictionary.Clear();
    XmlSerializer serializer = new XmlSerializer(typeof(List<Entry>));
    List<Entry> list = (List<Entry>)serializer.Deserialize(reader);
    foreach (Entry entry in list)
    {
        dictionary[entry.Key] = entry.Value;
    }
}
public class Entry
{
    public object Key;
    public object Value;
    public Entry()
    {
    }

    public Entry(object key, object value)
    {
        Key = key;
        Value = value;
    }
}

当键和值为字符串时,它生成如下输出。

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfEntry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Entry>
    <Key xsi:type="xsd:string">MyKey</Key>
    <Value xsi:type="xsd:string">MyValue</Value>  
  </Entry>
  <Entry>    
    <Key xsi:type="xsd:string">MyOtherKey</Key>    
    <Value xsi:type="xsd:string">MyOtherValue</Value>  
  </Entry>
</ArrayOfEntry>

6
你的第一个链接已经失效了 :( - jv42
2
使用http://web.archive.org/web/20100703052446/http://blogs.msdn.com/b/psheill/archive/2005/04/09/406823.aspx可以获取第一个链接的副本。 - David Paxson
1
我发现在我的情况下,非英语版本是最有用的。 - David Keaveny
请避免仅提供链接作为答案。:( - jeromej

13

请尝试这种替代简单的方法:

    void Main()
    {
        var model = new Foo() 
        { 
            RealDictionary = new Dictionary<string, int> { {"A", 23}, {"B", 40} } 
        };
        model.RealDictionary.Add("C", 69);
        
        using (var writer = XmlWriter.Create("c:\\test1.xml"))
            (new XmlSerializer(typeof(Foo))).Serialize(writer, model);
    }
    
    [Serializable]
    public class Foo
    {
        [XmlArray(ElementName ="Elements")]
        [XmlArrayItem(ElementName = "Element")]
        public List<KeyValueElement> SerializableDictionary 
        { 
            get { return RealDictionary.Select(x => new KeyValueElement {Key = x.Key, Value = x.Value}).ToList(); } 
            set { RealDictionary = value.ToDictionary(x=> x.Key, x => x.Value); }
        }
    
        [XmlIgnore]
        public Dictionary<string, int> RealDictionary {get;set;}  
    }
    
    [Serializable]
    public class KeyValueElement
    {
        public string Key { get; set; }
        public int Value { get; set; }  
    }

以下是 XML 结果:

    <Foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <Elements>
            <Element>
                <Key>A</Key><Value>23</Value>
            </Element>
            <Element>
                <Key>B</Key><Value>40</Value>
            </Element>
            <Element>
                <Key>C</Key><Value>69</Value>
            </Element>
        </Elements>
    </Foo>

10

我已经使用下面的内容一段时间了。它最初来自这里

namespace SerializeDictionary
{
    using System;
    using System.Collections.Generic;
    using System.Runtime.Serialization;
    using System.Xml;
    using System.Xml.Schema;
    using System.Xml.Serialization;

    /// <summary>
    /// Represents an XML serializable collection of keys and values.
    /// </summary>
    /// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam>
    /// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
    [Serializable]
    [XmlRoot("dictionary")]
    public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable
    {
        /// <summary>
        /// The default XML tag name for an item.
        /// </summary>
        private const string DefaultItemTag = "item";

        /// <summary>
        /// The default XML tag name for a key.
        /// </summary>
        private const string DefaultKeyTag = "key";

        /// <summary>
        /// The default XML tag name for a value.
        /// </summary>
        private const string DefaultValueTag = "value";

        /// <summary>
        /// The XML serializer for the key type.
        /// </summary>
        private static readonly XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));

        /// <summary>
        /// The XML serializer for the value type.
        /// </summary>
        private static readonly XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));

        /// <summary>
        /// Initializes a new instance of the
        /// <see cref="SerializableDictionary&lt;TKey, TValue&gt;"/> class.
        /// </summary>
        public SerializableDictionary()
        {
        }

        /// <summary>
        /// Initializes a new instance of the
        /// <see cref="SerializableDictionary&lt;TKey, TValue&gt;"/> class.
        /// </summary>
        /// <param name="info">A
        /// <see cref="T:System.Runtime.Serialization.SerializationInfo"/> object
        /// containing the information required to serialize the
        /// <see cref="T:System.Collections.Generic.Dictionary`2"/>.
        /// </param>
        /// <param name="context">A
        /// <see cref="T:System.Runtime.Serialization.StreamingContext"/> structure
        /// containing the source and destination of the serialized stream
        /// associated with the
        /// <see cref="T:System.Collections.Generic.Dictionary`2"/>.
        /// </param>
        protected SerializableDictionary(SerializationInfo info, StreamingContext context) : base(info, context)
        {
        }

        /// <summary>
        /// Gets the XML tag name for an item.
        /// </summary>
        protected virtual string ItemTagName
        {
            get
            {
                return DefaultItemTag;
            }
        }

        /// <summary>
        /// Gets the XML tag name for a key.
        /// </summary>
        protected virtual string KeyTagName
        {
            get
            {
                return DefaultKeyTag;
            }
        }

        /// <summary>
        /// Gets the XML tag name for a value.
        /// </summary>
        protected virtual string ValueTagName
        {
            get
            {
                return DefaultValueTag;
            }
        }

        /// <summary>
        /// Gets the XML schema for the XML serialization.
        /// </summary>
        /// <returns>An XML schema for the serialized object.</returns>
        public XmlSchema GetSchema()
        {
            return null;
        }

        /// <summary>
        /// Deserializes the object from XML.
        /// </summary>
        /// <param name="reader">The XML representation of the object.</param>
        public void ReadXml(XmlReader reader)
        {
            var wasEmpty = reader.IsEmptyElement;

            reader.Read();
            if (wasEmpty)
            {
                return;
            }

            try
            {
                while (reader.NodeType != XmlNodeType.EndElement)
                {
                    this.ReadItem(reader);
                    reader.MoveToContent();
                }
            }
            finally
            {
                reader.ReadEndElement();
            }
        }

        /// <summary>
        /// Serializes this instance to XML.
        /// </summary>
        /// <param name="writer">The XML writer to serialize to.</param>
        public void WriteXml(XmlWriter writer)
        {
            foreach (var keyValuePair in this)
            {
                this.WriteItem(writer, keyValuePair);
            }
        }

        /// <summary>
        /// Deserializes the dictionary item.
        /// </summary>
        /// <param name="reader">The XML representation of the object.</param>
        private void ReadItem(XmlReader reader)
        {
            reader.ReadStartElement(this.ItemTagName);
            try
            {
                this.Add(this.ReadKey(reader), this.ReadValue(reader));
            }
            finally
            {
                reader.ReadEndElement();
            }
        }

        /// <summary>
        /// Deserializes the dictionary item's key.
        /// </summary>
        /// <param name="reader">The XML representation of the object.</param>
        /// <returns>The dictionary item's key.</returns>
        private TKey ReadKey(XmlReader reader)
        {
            reader.ReadStartElement(this.KeyTagName);
            try
            {
                return (TKey)keySerializer.Deserialize(reader);
            }
            finally
            {
                reader.ReadEndElement();
            }
        }

        /// <summary>
        /// Deserializes the dictionary item's value.
        /// </summary>
        /// <param name="reader">The XML representation of the object.</param>
        /// <returns>The dictionary item's value.</returns>
        private TValue ReadValue(XmlReader reader)
        {
            reader.ReadStartElement(this.ValueTagName);
            try
            {
                return (TValue)valueSerializer.Deserialize(reader);
            }
            finally
            {
                reader.ReadEndElement();
            }
        }

        /// <summary>
        /// Serializes the dictionary item.
        /// </summary>
        /// <param name="writer">The XML writer to serialize to.</param>
        /// <param name="keyValuePair">The key/value pair.</param>
        private void WriteItem(XmlWriter writer, KeyValuePair<TKey, TValue> keyValuePair)
        {
            writer.WriteStartElement(this.ItemTagName);
            try
            {
                this.WriteKey(writer, keyValuePair.Key);
                this.WriteValue(writer, keyValuePair.Value);
            }
            finally
            {
                writer.WriteEndElement();
            }
        }

        /// <summary>
        /// Serializes the dictionary item's key.
        /// </summary>
        /// <param name="writer">The XML writer to serialize to.</param>
        /// <param name="key">The dictionary item's key.</param>
        private void WriteKey(XmlWriter writer, TKey key)
        {
            writer.WriteStartElement(this.KeyTagName);
            try
            {
                keySerializer.Serialize(writer, key);
            }
            finally
            {
                writer.WriteEndElement();
            }
        }

        /// <summary>
        /// Serializes the dictionary item's value.
        /// </summary>
        /// <param name="writer">The XML writer to serialize to.</param>
        /// <param name="value">The dictionary item's value.</param>
        private void WriteValue(XmlWriter writer, TValue value)
        {
            writer.WriteStartElement(this.ValueTagName);
            try
            {
                valueSerializer.Serialize(writer, value);
            }
            finally
            {
                writer.WriteEndElement();
            }
        }
    }
}

7

以下是基于Gildor答案的更短版本:

[XmlElement("Dictionary")]
public List<KeyValuePair<string, string>> XMLDictionaryProxy
{
    get
    {
        return new List<KeyValuePair<string, string>>(this.Dictionary);
    }
    set
    {
        this.Dictionary = new Dictionary<string, string>();
        foreach (var pair in value)
            this.Dictionary[pair.Key] = pair.Value;
    }
}

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

祝您愉快。


4
无法正常工作 - KeyValuePair 不支持使用 XmlSerializable 进行序列化,因为 KeyValue 属性均为只读。 可能的解决方法可以在这里找到。 - bavaza

2
这个 SerializableDictionary 类可以像普通的 Dictionary 一样使用,并且可以进行 Serialize() 操作。

Gist

(注:原文中的代码已经翻译成了中文,保留了html标签和链接)

SerializableDictionary.cs

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

/// <summary>
/// Base on https://weblogs.asp.net/pwelter34/444961
/// </summary>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
[XmlRoot("dictionary")]
public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable
{
    // XmlSerializer.Deserialize() will create a new Object, and then call ReadXml()
    // So cannot use instance field, use class field.

    public static string itemTag = "item";
    public static string keyTag = "key";
    public static string valueTag = "value";

    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        if (reader.IsEmptyElement)
            return;

        var keySerializer = new XmlSerializer(typeof(TKey));
        var valueSerializer = new XmlSerializer(typeof(TValue));

        reader.ReadStartElement();

        // IsStartElement() will call MoveToContent()
        // reader.MoveToContent();
        while (reader.IsStartElement(itemTag))
        {
            reader.ReadStartElement(itemTag);

            reader.ReadStartElement(keyTag);
            TKey key = (TKey)keySerializer.Deserialize(reader);
            reader.ReadEndElement();

            reader.ReadStartElement(valueTag);
            TValue value = (TValue)valueSerializer.Deserialize(reader);
            reader.ReadEndElement();

            reader.ReadEndElement();
            this.Add(key, value);

            // IsStartElement() will call MoveToContent()
            // reader.MoveToContent();
        }
        reader.ReadEndElement();
    }

    public void WriteXml(XmlWriter writer)
    {
        var keySerializer = new XmlSerializer(typeof(TKey));
        var valueSerializer = new XmlSerializer(typeof(TValue));

        foreach (var kvp in this)
        {
            writer.WriteStartElement(itemTag);

            writer.WriteStartElement(keyTag);
            keySerializer.Serialize(writer, kvp.Key);
            writer.WriteEndElement();

            writer.WriteStartElement(valueTag);
            valueSerializer.Serialize(writer, kvp.Value);
            writer.WriteEndElement();

            writer.WriteEndElement();
        }
    }
}

Demo.cs

using System;
using System.IO;
using System.Xml.Serialization;

//[XmlRoot("AnimalDictionary")]
public class SDict : SerializableDictionary<string, string>
{
}

namespace Test
{
    public class Program
    {

        static void Main(string[] args)
        {
            var dict = new SDict();
            //SDict.itemTag = "AnimalCount";
            //SDict.keyTag = "Type";
            //SDict.valueTag = "Count";
            dict.Add("Cat", "1");
            dict.Add("Dog", "2");

            foreach (var kvp in dict)
                Console.WriteLine(kvp);

            XmlSerializer serializer = new XmlSerializer(typeof(SDict));

            using (var writer = new StreamWriter("dict.xml"))
            {
                serializer.Serialize(writer, dict);
            }

            Console.WriteLine("\nXML File:");
            Console.WriteLine(File.ReadAllText("dict.xml"));

            using (var reader = new StreamReader("dict.xml"))
            {
                dict = serializer.Deserialize(reader) as SDict;
            }

            Console.WriteLine("\nAfter Deserialize");

            foreach (var kvp in dict)
                Console.WriteLine(kvp);

            Console.ReadLine();
        }
    }
}

0

如果你正在学习 C#,那么你可以创建一个带有所需逻辑的类。例如,我创建了一个ProgressiveTax对象,您可以使用.Evaluate()调用它来计算税收。

您还可以从XML字符串中写入或读取数据(可以将其写入文件)

例如,首先从提供的PAYE信息中填充税收分档并保存到文件PAYE.xml中。然后忘记税收分档(超出{ }的范围)。最后从文件中读取以填充税收表。

static class Program
{
    static void Main(string[] args)
    {
        {   
            // create a tax table and save it to a file
            var tax = ProgressiveTax.PAYE();
            File.WriteAllText("PAYE.xml", tax.ToXml());
        }
        {   
            // read a tax table from a file
            var tax = ProgressiveTax.FromXml(File.ReadAllText("PAYE.xml"));

            // use the tax table
            var x = tax.Evaluate(42250m);
            Debug.WriteLine($"Tax={x}");
        }            
    }
}

这个 XML 文件看起来像这样,它可以手动编辑,也可以从数据库/网络服务生成。

<?xml version="1.0" encoding="utf-16"?>
<ProgressiveTax xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Credit="2400">
  <Brackets>
    <Bracket>
      <Item1>0</Item1>
      <Item2>0.1</Item2>
    </Bracket>
    <Bracket>
      <Item1>24000</Item1>
      <Item2>0.15</Item2>
    </Bracket>
    <Bracket>
      <Item1>40667</Item1>
      <Item2>0.2</Item2>
    </Bracket>
    <Bracket>
      <Item1>57334</Item1>
      <Item2>0.25</Item2>
    </Bracket>
  </Brackets>
</ProgressiveTax>

实际持有税收信息并计算税额的类

public class ProgressiveTax
{
    public ProgressiveTax()
    {
        this.Table = new SortedDictionary<decimal, float>();
    }
    public ProgressiveTax(SortedDictionary<decimal, float> table)
    {
        this.Table = table;
    }

    public static ProgressiveTax PAYE()
    {
        var tax = new ProgressiveTax();
        tax.Credit = 2400m;
        tax.Table[0m] = 0.1f;
        tax.Table[24000m] = 0.15f;
        tax.Table[40667m] = 0.20f;
        tax.Table[57334m] = 0.25f;
        return tax;
    }

    public string ToXml()
    {
        var fs = new StringWriter();
        var xs = new XmlSerializer(typeof(ProgressiveTax));
        xs.Serialize(fs, this);
        fs.Close();
        return fs.ToString();
    }

    public static ProgressiveTax FromXml(string xml)
    {
        var fs = new StringReader(xml);
        var xs = new XmlSerializer(typeof(ProgressiveTax));
        var tax = xs.Deserialize(fs) as ProgressiveTax;
        fs.Close();
        return tax;
    }

    [XmlAttribute]
    public decimal Credit { get; set; }
  
    [XmlIgnore()]  
    SortedDictionary<decimal, float> Table { get; }

    [XmlArrayItem(ElementName = "Bracket")]
    public (decimal lower, float rate)[] Brackets
    {
        get
        {
            var parts = new (decimal lower, float rate)[Table.Count];
            int index = 0;
            foreach (var item in Table)
            {
                parts[index++] = (item.Key, item.Value);
            }
            return parts;
        }
        set
        {
            Table.Clear();
            foreach (var (lower, rate) in value)
            {
                Table[lower] = rate;
            }
        }
    }

    public decimal Evaluate(decimal income)
    {
        decimal result = -Credit;
        foreach (var item in Table.Reverse())
        {
            if (item.Key <= income)
            {
                Debug.WriteLine($"Assess {item.Value:P2} tax on {income - item.Key}");
                result += (decimal)( item.Value * (float) (income - item.Key));
                income = item.Key;
            }
        }
        return Math.Max(0m, result);
    }
}

示例程序在调试器中产生以下输出结果。

Assess 20.00% tax on 1583
Assess 15.00% tax on 16667
Assess 10.00% tax on 24000
Tax=2816.65

如果您将 1583 + 16667 + 24000 = 42250 相加,那么这就是总收入。由于这是一项累进税,因此使用上述税率和金额,然后再加上2400的抵免。同时,结果必须为0或正数。

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