【问题标题】:What is the correct way to serialize nullable types?序列化可空类型的正确方法是什么?
【发布时间】:2009-08-18 02:20:03
【问题描述】:

我正在我的一个类中实现 IXMLSerializable。它包含一些可以为空的数字属性(int?double?等)

通过 IXMLSerializable 序列化/序列化这些的正确方法是什么?这是我现在正在做的,它有效,但显然不是正确的方法。

void IXmlSerializable.WriteXml(XmlWriter writer)
{
    ...

    if (this._PropName == null)
    {
        writer.WriteElementString("PropName", "NULL");
    }
    else
    {
        writer.WriteElementString("PropName", this._PropName.ToString());
    }
    ...
}

void IXmlSerializable.ReadXml(XmlReader reader)    
{
    string tempStr;
    ...

    reader.ReadStartElement("PropName"); 

    if (tempStr != "NULL")
    {
        this._PropName = double.Parse(tempStr);
    }
    else 
    {
        this._PropName = null;
    }
    ...
}

更新: 有人要求我介绍一下我为什么要实现 IXmlSerializable 的背景。我正在开发一个建筑设计程序,我需要一个代表楼层集合的类。每个 Floor 都具有诸如 Floor.Area 面积、Floor.Height 等属性。然而,楼层的标高由总和定义它下面的楼层高度。因此,每当 Floor.Height 属性更改或 FloorCollection 被修改时,都会重新计算 Floors 的标高。

我需要序列化的 FloorCollection 类继承自 BindingList。如果我尝试直接序列化此类,它将序列化楼层的集合,但不会序列化类中的任何属性或字段。见我的previous post on this

现在我正在尝试添加限制集合中建筑物楼层的最大高度、最大顶部标高和最小底部标高的功能。所以我使用可为空的双精度来表示这些限制,其中空值表示不受限制。高程属性可以是正数、负数或零。因此,需要有一个备用状态 null 来标识何时没有限制。

现在我认为,如果有一个单独的布尔值来标识是否存在海拔/高度限制,然后有一个常规的 double 属性来标识启用它的限制是什么,总体上可能会更容易。

【问题讨论】:

  • 这并不可怕,但考虑属性是字符串而不是双精度的情况。字符串的正确标志值是什么?
  • 实施 IXmlSerializable 通常是 IMO 的坏事。你失去了所有的简单性和良好的模式,这使得它首先是令人向往的。
  • @Eric,我认为K Marc 有一个观点,Scott 提出了一些问题。您能否解释一下您一般使用 XML 序列化并具体实现 IXmlSerializable 的目的是什么?它可以帮助我们了解最适合您需求的答案。
  • 谢谢。实际上,我认为它可以为空就可以了。这比在另一个属性设置为 false 时忽略一个值更清楚。当然,我更喜欢 Decimal 以加倍。

标签: c# serialization nullable ixmlserializable


【解决方案1】:

您希望始终为属性编写 XML,但如果属性值为 null,您希望包含 xsi:nil="true" 属性。

void IXmlSerializable.WriteXml(XmlWriter writer)
{
    ...

    if (this._PropName == null)
    {
        writer.WriteStartElement("PropName");
        writer.WriteAttributeString("xsi", "nil", "http://www.w3.org/2001/XMLSchema-instance", "true");
        writer.WriteEndElement();
    }
    else
    {
        writer.WriteElementString("PropName", this._PropName.ToString());
    }
    ...
}

您可能还想编写一个 xsi:type="xsd:datatype" 属性,其中 xsd 是 http://www.w3.org/2001/XMLSchema 命名空间。这将允许您在反序列化期间重新读取数据类型,以了解是否(以及如何)转换值。

【讨论】:

  • +1 表示 xsi:nil,-1 表示坚持 xsi:type。完全自描述的数据肯定有一个地方,但通常不是序列化。
  • @Steven Sudit:我不同意。查看任何数据契约序列化的 XML,您几乎肯定会看到类型信息。
  • @Scott:说实话,我真的不确定 Eric 的意图是什么。我继续假设他们想要将类型扁平化为字符串以实现持久性,就像在数据库中一样。如果是这样,那么 null=omited 是一个很好的答案。但是,如果他们在 WCF 中使用该类,那么您的回答会更有意义。我问埃里克他的目标是什么;也许这将有助于解决这个问题。
  • 见我上面的更新。我正在为一个程序编写一个插件,我可以在其中将数据写入附加到文件中本机对象的字符串。因此,我的任何自定义对象都需要能够序列化为字符串,以便可以将其保存到用户正在使用的文件中并从中重新初始化。
  • 对于它的价值,我在这里仍然看不到 xsi:type 的价值,但我对 xsi:nil 很好。
【解决方案2】:

如果元素为空,则省略该元素。

编辑

考虑一下如果您制作了一个添加了属性的新版本会发生什么。如果您随后反序列化旧版本的副本,则包含新属性的新元素将丢失,因此您可以正确地将属性保留为未初始化,为 null。这是相同的逻辑,扩展到涵盖现在时。

编辑

有关如何告诉 XmlSerializer 在不编写自定义序列化程序的情况下省略空属性的信息,请查看 this。此外,鉴于背景信息,虽然我仍然更喜欢省略 xsi:nil,但我现在认为后者是可以接受的解决方案。

【讨论】:

    【解决方案3】:

    你可以写一个null:

    writer.WriteElementString("Test", null);
    

    它只会产生像<Test/>这样的元素

    或者更具体地了解空值,您可以使用xsi:nil 属性:

    const string xsiNs = "http://www.w3.org/2001/XMLSchema-instance";
    using (XmlWriter writer = XmlWriter.Create(Console.Out))
    {
      writer.WriteStartElement("Test");
      writer.WriteAttributeString("xsi", "nil", xsiNs, "true");
      writer.WriteEndElement();
    }
    

    这将导致<Test xsi:nil="true"/>

    【讨论】:

    • +1 用于调出 xsi:nil。我可能不会在内部序列化中使用它,但我肯定将它用作更正式的外部数据交换的一部分,尤其是通过 SOAP。
    • 我觉得 还不错。但是我如何将它作为值或 null 读回?
    • 你需要一个自定义的反序列化器,这听起来太麻烦了。
    【解决方案4】:
    void IXmlSerializable.WriteXml(XmlWriter writer)
    {
      if (this._Foo == null)
      {
        writer.WriteStartElement("Foo");
        writer.WriteEndElement("Foo");
      }
      else
      {
        writer.WriteElementString("Foo", this._Foo.ToString());
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2015-12-13
      • 2017-12-29
      • 1970-01-01
      • 1970-01-01
      • 2019-01-12
      • 1970-01-01
      • 2010-09-10
      • 1970-01-01
      相关资源
      最近更新 更多