如何序列化ICollection<;T>;它还具有对XML的读/写属性

本文关键字:XML 属性 的读 ICollection 序列化 lt gt | 更新日期: 2023-09-27 18:26:33

我有一个实现自定义类列表的类。该类还有两个属性。但当我序列化那个类时,XML只包含我的自定义类的数组,而不包含另外两个属性。这是类别:

public class Porudzbina : List<PorudzbenicaStavka>, IEnumerable<SqlDataRecord>
{
    public long KomSifra { get; set; }
    public Guid KomId { get; set; }
    IEnumerator<SqlDataRecord> IEnumerable<SqlDataRecord>.GetEnumerator()
    {
        var sqlRow = new SqlDataRecord(
              new SqlMetaData("rb", SqlDbType.Int),
              new SqlMetaData("RobaSifra", SqlDbType.NVarChar, 50),
              new SqlMetaData("RobaNaziv", SqlDbType.NVarChar, 100)
             );
        foreach (PorudzbenicaStavka por in this)
        {
            sqlRow.SetInt32(0, por.rb);
            sqlRow.SetString(1, por.RobaSifra);
            sqlRow.SetString(2, por.RobaNaziv);
            yield return sqlRow;
        }
    }
}

以及我用来序列化它的代码:

    XmlSerializer serializer = new XmlSerializer(typeof(Porudzbina));
    using (TextWriter writer = new StreamWriter(@"C:'Xmle.xml"))
    {
        serializer.Serialize(writer, por);
    } 

这就是我得到的XML:

    <?xml version="1.0" encoding="utf-8"?>
<ArrayOfPorudzbenicaStavka xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <PorudzbenicaStavka>
    <rb>1</rb>
    <RobaSifra>3702</RobaSifra>
    <RobaNaziv>Foullon mlecna cokolada 33% Ecuador 100g</RobaNaziv>    
  </PorudzbenicaStavka>
  <PorudzbenicaStavka>
    <rb>2</rb>
    <RobaSifra>1182</RobaSifra>
    <RobaNaziv>IL Capitano zelena maslina sa paprikom 720g</RobaNaziv>    
  </PorudzbenicaStavka>
  <PorudzbenicaStavka>
    <rb>3</rb>
    <RobaSifra>1120</RobaSifra>
    <RobaNaziv>Kaiser tuna steak sa papricicom u ulju 170g.</RobaNaziv>    
  </PorudzbenicaStavka>
</ArrayOfPorudzbenicaStavka>

我希望我的xml包含两个属性以及一个自定义类数组,这样我就可以将其反序列化为原始状态。。。

如何序列化ICollection<;T>;它还具有对XML的读/写属性

在文档部分"序列化实现ICollection接口的类:"中解释了属性未反序列化的原因

您可以通过实现ICollection接口创建自己的集合类,并使用XmlSerializer序列化这些类的实例。请注意,当一个类实现ICollection接口时,只有该类包含的集合被序列化任何添加到类中的公共属性或字段都不会被序列化

所以,就是这样。

您可能会考虑更改设计,使类不具有属性。有关进行此更改的某些原因,请参阅为什么不从列表继承?。

然而,如果您选择具有可序列化属性的集合,则需要手动实现IXmlSerializable。这很麻烦,因为您需要处理许多"边缘"情况,包括空元素、意外元素、注释以及是否存在空白,所有这些都可能会使ReadXml()方法失效。有关一些背景知识,请参阅如何正确实现IXmlSerializable。

首先,为具有可序列化属性的泛型列表创建一个基类:

public class XmlSerializableList<T> : List<T>, IXmlSerializable where T : new()
{
    public XmlSerializableList() : base() { }
    public XmlSerializableList(IEnumerable<T> collection) : base(collection) { }
    public XmlSerializableList(int capacity) : base(capacity) { }
    #region IXmlSerializable Members
    const string CollectionItemsName = "Items";
    const string CollectionPropertiesName = "Properties";
    void IXmlSerializable.WriteXml(XmlWriter writer)
    {
        // Do not write the wrapper element.
        // Serialize the collection.
        WriteCollectionElements(writer);
        // Serialize custom properties.
        writer.WriteStartElement(CollectionPropertiesName);
        WriteCustomElements(writer);
        writer.WriteEndElement();
        // Do not end the wrapper element.
    }
    private void WriteCollectionElements(XmlWriter writer)
    {
        if (Count < 1)
            return;
        // Serialize the collection.
        writer.WriteStartElement(CollectionItemsName);
        var serializer = new XmlSerializer(typeof(T));
        var ns = new XmlSerializerNamespaces();
        ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
        foreach (var item in this)
        {
            serializer.Serialize(writer, item, ns);
        }
        writer.WriteEndElement();
    }
    /// <summary>
    /// Write ALL custom elements to the XmlReader
    /// </summary>
    /// <param name="writer"></param>
    protected virtual void WriteCustomElements(XmlWriter writer)
    {
    }
    void IXmlSerializable.ReadXml(XmlReader reader)
    {
        if (reader.IsEmptyElement)
        {
            reader.Read();
            return;
        }
        reader.ReadStartElement(); // Advance to the first sub element of the wrapper element.
        while (reader.NodeType != XmlNodeType.EndElement)
        {
            if (reader.NodeType != XmlNodeType.Element)
                // Comment, whitespace
                reader.Read();
            else if (reader.IsEmptyElement)
                reader.Read();
            else if (reader.Name == CollectionItemsName)
                ReadCollectionElements(reader);
            else if (reader.Name == CollectionPropertiesName)
                ReadCustomElements(reader);
            else
                // Unknown element, skip it.
                reader.Skip();
        }
        // Move past the end of the wrapper element
        reader.ReadEndElement();
    }
    void ReadCustomElements(XmlReader reader)
    {
        reader.ReadStartElement(); // Advance to the first sub element of the collection element.
        while (reader.NodeType != XmlNodeType.EndElement)
        {
            if (reader.NodeType == XmlNodeType.Element)
            {
                using (var subReader = reader.ReadSubtree())
                {
                    while (subReader.NodeType != XmlNodeType.Element) // Read past XmlNodeType.None
                        if (!subReader.Read())
                            break;
                    ReadCustomElement(subReader);
                }
            }
            reader.Read();
        }
        // Move past the end of the properties element
        reader.Read();
    }
    void ReadCollectionElements(XmlReader reader)
    {
        var serializer = new XmlSerializer(typeof(T));
        reader.ReadStartElement(); // Advance to the first sub element of the collection element.
        while (reader.NodeType != XmlNodeType.EndElement)
        {
            if (reader.NodeType == XmlNodeType.Element)
            {
                using (var subReader = reader.ReadSubtree())
                {
                    while (subReader.NodeType != XmlNodeType.Element) // Read past XmlNodeType.None
                        if (!subReader.Read())
                            break;
                    var item = (T)serializer.Deserialize(subReader);
                    Add(item);
                }
            }
            reader.Read();
        }
        // Move past the end of the collection element
        reader.Read();
    }
    /// <summary>
    /// Read ONE custom element from the XmlReader
    /// </summary>
    /// <param name="reader"></param>
    protected virtual void ReadCustomElement(XmlReader reader)
    {
    }
    XmlSchema IXmlSerializable.GetSchema()
    {
        return null;
    }
    #endregion
}

若要使用此类,您需要重写ReadCustomElement(XmlReader reader)WriteCustomElements(XmlWriter writer),前者读取单个自定义属性,后者写入所有定制属性。(注意不对称性,它使实现类层次结构变得更容易。)然后按如下方式创建Porudzbina类:

public class Porudzbina : XmlSerializableList<PorudzbenicaStavka>
{
    public long KomSifra { get; set; }
    public Guid KomId { get; set; }
    const string KomSifraName = "KomSifra";
    const string KomIdName = "KomId";
    protected override void WriteCustomElements(XmlWriter writer)
    {
        writer.WriteElementString(KomSifraName, XmlConvert.ToString(KomSifra));
        writer.WriteElementString(KomIdName, XmlConvert.ToString(KomId));
        base.WriteCustomElements(writer);
    }
    protected override void ReadCustomElement(XmlReader reader)
    {
        if (reader.Name == KomSifraName)
        {
            KomSifra = reader.ReadElementContentAsLong();
        }
        else if (reader.Name == KomIdName)
        {
            var s = reader.ReadElementContentAsString();
            KomId = XmlConvert.ToGuid(s);
        }
        else
        {
            base.ReadCustomElement(reader);
        }
    }
}

这将创建如下的XML:

<Porudzbina>
    <Items>
        <PorudzbenicaStavka>
            <!-- contents of first PorudzbenicaStavka -->
        </PorudzbenicaStavka>
        <!-- Additional PorudzbenicaStavka -->
    </Items>
    <Properties>
        <KomSifra>101</KomSifra>
        <KomId>bb23a3b8-23d3-4edd-848b-d7621e6ed2c0</KomId>
    </Properties>
</Porudzbina>

我已经将我的序列化库上传到Github,在那里处理这些问题。

Atlas Xml序列化程序

我假设您有以下数据类。我刚刚在属性上添加了[XmlElement]属性,以强制它们序列化为xml元素。

public class Porudzbina : List<PorudzbenicaStavka>, IEnumerable<SqlDataRecord>
{
    [XmlElement]
    public long KomSifra { get; set; }
    [XmlElement]
    public Guid KomId { get; set; }
    IEnumerator<SqlDataRecord> IEnumerable<SqlDataRecord>.GetEnumerator()
    {
        var sqlRow = new SqlDataRecord(
              new SqlMetaData("rb", SqlDbType.Int),
              new SqlMetaData("RobaSifra", SqlDbType.NVarChar, 50),
              new SqlMetaData("RobaNaziv", SqlDbType.NVarChar, 100)
             );
        foreach (PorudzbenicaStavka por in this)
        {
            sqlRow.SetInt32(0, por.rb);
            sqlRow.SetString(1, por.RobaSifra);
            sqlRow.SetString(2, por.RobaNaziv);
            yield return sqlRow;
        }
    }
}
public class PorudzbenicaStavka
{
    [XmlElement]
    public int rb { get; set; }
    [XmlElement]
    public string RobaSifra { get; set; }
    [XmlElement]
    public string RobaNaziv { get; set;  }
}

举个例子:

var o = new Porudzbina
{
    new PorudzbenicaStavka { rb=1, RobaSifra="3702", RobaNaziv="Foullon mlecna cokolada 33% Ecuador 100g" },
    new PorudzbenicaStavka { rb=2, RobaSifra="1182", RobaNaziv="IL Capitano zelena maslina sa paprikom 720g" },
    new PorudzbenicaStavka { rb=3, RobaSifra="1120", RobaNaziv="Kaiser tuna steak sa papricicom u ulju 170g." },
};
o.KomId = new Guid("{EC63AEC3-1512-451F-B967-836DD0E9820A}");
o.KomSifra = 999999;

以下是atlas xml序列化库的工作原理:

var serialized = Atlas.Xml.Serializer.Serialize(o, true);
var deserialized = Atlas.Xml.Serializer.Deserialize<Porudzbina>(serialized);

Xml看起来像这样:

<Porudzbina>
  <KomSifra>999999</KomSifra>
  <KomId>ec63aec3-1512-451f-b967-836dd0e9820a</KomId>
  <item>
    <rb>1</rb>
    <RobaSifra>3702</RobaSifra>
    <RobaNaziv>Foullon mlecna cokolada 33% Ecuador 100g</RobaNaziv>
  </item>
  <item>
    <rb>2</rb>
    <RobaSifra>1182</RobaSifra>
    <RobaNaziv>IL Capitano zelena maslina sa paprikom 720g</RobaNaziv>
  </item>
  <item>
    <rb>3</rb>
    <RobaSifra>1120</RobaSifra>
    <RobaNaziv>Kaiser tuna steak sa papricicom u ulju 170g.</RobaNaziv>
  </item>
</Porudzbina>

如果你这样更改数据类:

[Atlas.Xml.XmlSerializationType(ChildElementName = "PorudzbenicaStavka")]
public class Porudzbina : List<PorudzbenicaStavka>, IEnumerable<SqlDataRecord>
{
    public long KomSifra { get; set; }
    public Guid KomId { get; set; }
    // ...
}
public class PorudzbenicaStavka
{
    public int rb { get; set; }
    public string RobaSifra { get; set; }
    [XmlText]
    public string RobaNaziv { get; set;  }
}

然后,序列化类将是这样的:

<Porudzbina KomSifra="999999" KomId="ec63aec3-1512-451f-b967-836dd0e9820a">
  <PorudzbenicaStavka rb="1" RobaSifra="3702">Foullon mlecna cokolada 33% Ecuador 100g</PorudzbenicaStavka>
  <PorudzbenicaStavka rb="2" RobaSifra="1182">IL Capitano zelena maslina sa paprikom 720g</PorudzbenicaStavka>
  <PorudzbenicaStavka rb="3" RobaSifra="1120">Kaiser tuna steak sa papricicom u ulju 170g.</PorudzbenicaStavka>
</Porudzbina>