【问题标题】:How to make a value type nullable with .NET XmlSerializer?如何使用 .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(零)时。


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

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

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

【问题讨论】:

    标签: c# xml-serialization null value-type


    【解决方案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; }
            }
            

            【讨论】:

              猜你喜欢
              • 2010-10-10
              • 1970-01-01
              • 2011-05-12
              • 1970-01-01
              • 2019-06-06
              • 2020-02-10
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多