如何使用 .NET XmlSerializer 使值类型可以为空?

Posted

技术标签:

【中文标题】如何使用 .NET XmlSerializer 使值类型可以为空?【英文标题】:How to make a value type nullable with .NET XmlSerializer? 【发布时间】:2010-10-16 17:33:27 【问题描述】:

假设我有这个对象:

[Serializable]
public class MyClass

    public int Age  get; set; 
    public int MyClassB  get; set; 

[Serializable]
public class MyClassB

    public int RandomNumber  get; set; 

XmlSerializer 会像这样序列化对象:

<MyClass>
    <Age>0</age>
    <MyClassB>
        <RandomNumber>4234</RandomNumber>
    </MyClassB>
</MyClass>

如何使属性 Age 可以为空? IE: 属性 Age 小于 0 时不序列化?

我尝试使用 Nullable,但它像这样序列化我的对象:

<MyClass>
    <Age d5p1:nil="true" />
    <MyClassB>
        <RandomNumber>4234</RandomNumber>
    </MyClassB>
</MyClass>    

通过阅读 MSDN 文档,我发现:

您不能将 IsNullable 属性应用于类型为值类型的成员,因为值类型不能包含 nullNothingnullptra 空引用(在 Visual Basic 中为 Nothing)。此外,对于可为空的值类型,您不能将此属性设置为 false。当此类类型为 nullNothingnullptra 空引用(Visual Basic 中为 Nothing)时,它们将通过将 xsi:nil 设置为 true 来进行序列化。

来源:http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlelementattribute.isnullable.aspx

我了解值类型不能设置为 null。值类型总是设置为某事。序列化无法根据当前值决定是否序列化。

我尝试了属性,但没有成功。我尝试创建一个 agecontainer 对象并使用属性操作它的序列化,但没有成功。

我真正想要的是:

<MyClass>
    <MyClassB>
        <RandomNumber>4234</RandomNumber>
    </MyClassB>
</MyClass>

当属性 Age 小于 0(零)时。


看来您必须实现自定义序列化。

是的,我也是这么想的,但我想离开它。

在应用程序中,对象要复杂得多,我不想自己处理序列化。

【问题讨论】:

【参考方案1】:

我刚刚发现了这一点。 XmlSerialier 查找 XXXSpecified 布尔属性以确定是否应包含它。这应该可以很好地解决问题。

[Serializable]
public class MyClass

  public int Age  get; set; 
  [XmlIgnore]
  public bool AgeSpecified  get  return Age >= 0;  
  public int MyClassB  get; set; 


[Serializable]
public class MyClassB

  public int RandomNumber  get; set; 

证明:

static string Serialize<T>(T obj)

  var serializer = new XmlSerializer(typeof(T));
  var builder = new StringBuilder();
  using (var writer = new StringWriter(builder))
  
    serializer.Serialize(writer, obj);
    return builder.ToString();
  


static void Main(string[] args)

  var withoutAge = new MyClass()  Age = -1 ;
  var withAge = new MyClass()  Age = 20 ;

  Serialize(withoutAge); // = <MyClass><MyClassB>0</MyClassB></MyClass>
  Serialize(withAge); // = <MyClass><Age>20</Age><MyClassB>0</MyClassB></MyClass>


编辑:是的,这是一个文档化的功能。见MSDN entry for XmlSerializer

另一种选择是使用特殊模式来创建 XmlSerializer 识别的布尔字段,并将 XmlIgnoreAttribute 应用于该字段。该模式以 propertyNameSpecified 的形式创建。例如,如果有一个名为“MyFirstName”的字段,您还将创建一个名为“MyFirstNameSpecified”的字段,指示 XmlSerializer 是否生成名为“MyFirstName”的 XML 元素。

【讨论】:

+1 这是一个巧妙的技巧!您知道它是未记录的功能还是完全受支持? @James 在可空之前就已经存在了,所以它应该可以正常工作:) 我前段时间使用过它,没有遇到任何问题。 y,就在 xml 序列化程序类:msdn.microsoft.com/en-us/library/… --- 在“覆盖默认序列化”的正上方寻找 Specified 哇,这简直是骇人听闻!哈哈。但它是做这项工作的。而且还不错。感谢您的回答。非常感谢。点击为“接受的答案”。 我实际上更喜欢对此进行细微的改动。将 age 设为可为空的 int,然后在 AgeSpecified 属性中条件为 (Age != null)。这使您可以在没有诸如零之类的标记值的情况下获得所需的语义。【参考方案2】:

这应该会有所帮助 制作年龄int? 和..

public bool ShouldSerializeAge()  return Age.HasValue; 

..这确实意味着将 ShouldSerializeXXX 方法添加到您的课程中!

【讨论】:

另外,根据 MSDN 文档,我们应该实现 resetXXX 方法,它会再次将其设置为 null。这两种方法似乎可以协同工作。【参考方案3】:

您需要进行自定义 XML 序列化;见IXmlSerializer。

public class MyClass : IXmlSerializable

    public int Age  get; set; 
    public MyClassB MyClassB  get; set; 

    public System.Xml.Schema.XmlSchema GetSchema()
    
        // http://msdn.microsoft.com/en-us/library/system.xml.serialization.ixmlserializable.aspx
        return null;
    

    public void ReadXml(XmlReader reader)
    
        if (reader.IsStartElement("Age"))
            Age = reader.ReadContentAsInt();

        var serializer = new XmlSerializer(typeof(MyClassB));
        MyClassB = (MyClassB)serializer.Deserialize(reader);
    

    public void WriteXml(XmlWriter writer)
    
        if (Age > 0)
        
            writer.WriteStartElement("Age");
            writer.WriteValue(Age);
            writer.WriteEndElement();
        

        var serializer = new XmlSerializer(typeof(MyClassB));
        serializer.Serialize(writer, MyClassB);
    

【讨论】:

【参考方案4】:

忘记 Nullable ... ShouldSerializeXXX 是一个很好的解决方案。 此处年龄将根据您的情况进行序列化。

[Serializable]
public class MyClass

    public int Age  get; set; 
    public int MyClassB  get; set; 

    #region Conditional Serialization
    public bool ShouldSerializeAge()  return age > 0; 
    #endregion


[Serializable]
public class MyClassB

    public int RandomNumber  get; set; 

【讨论】:

【参考方案5】:

将 Samuel 的回答和 Greg Beech 的评论扩展到布尔属性的情况:如果属性是 bool 类型,那么您不能在 propertySpecified 属性中编写简单的测试。

一个解决方案是使用 Nullable 类型,那么在 propertySpecified 属性中的测试就是 property.HasValue。例如

using System.Xml.Serialization;

public class Person

    public bool? Employed  get; set; 

    [XmlIgnore]
    public bool EmployedSpecified  get  return Employed.HasValue;  

对数值属性使用可为空类型的替代方法(Greg Beech 建议)是将 value 属性设置为无效的默认值,例如 -1,如下所示:

using System.ComponentModel;
using System.Xml.Serialization;

public class Person

    [DefaultValue(-1)]
    public int Age  get; set; 

    [XmlIgnore]
    public bool AgeSpecified  get  return Age >= 0;  

【讨论】:

【参考方案6】:

如果您将元素的 'minoccurs' 属性设置为 'minoccurs="0"',xsd.exe 将自动生成 XXXSpecified 属性和访问器...如果您使用架构来定义您的 xml/类

【讨论】:

【参考方案7】:

您可以使用XmlElementAttribute.IsNullable

[Serializable]
public class MyClass

    [XmlElement(IsNullable = true)]
    public int? Age  get; set; 

    public int MyClassB  get; set; 

【讨论】:

以上是关于如何使用 .NET XmlSerializer 使值类型可以为空?的主要内容,如果未能解决你的问题,请参考以下文章

.NET 6 XmlSerializer 漂亮的打印

XmlSerializer 空引用异常出现在 .NET 4.0 但不在 .NET 4.5

使用 XmlSerializer 将 XML 反序列化为类型

Younge学习.NET反序列化漏洞

强制 ASMX 代理使用 XmlSerializer 而不是 DataContractSerializer

无法使用 c# xmlserializer 反序列化以前序列化的 XML