CodeGym /课程 /C# SELF /设置 XML 序列化

设置 XML 序列化

C# SELF
第 48 级, 课程 1
可用

1. 介绍

你可能已经知道,.NET 默认会基于 public 字段和属性把对象序列化/反序列化成 XML。但当你想生成漂亮、和别的程序或标准兼容的 XML,或者不想把某些内部细节放进 XML 时,就需要更细的控制。

在 C# 里,这就是靠 序列化属性 来做的——给类、属性或字段打的“标签”。有了这些属性,你几乎能控制一切:哪些成员会出现在 XML、哪些被忽略、元素叫什么名字、是不是变成属性、是否有复杂结构,甚至元素的顺序!

可以把默认序列化想象成自动填表:名字、姓氏、中间名照位置随便填。用属性的话你就像个有权力的文书:决定哪一行写哪儿、跳过哪些、标红哪些、把某些内容藏到机密章下面。

对象与 XML 的映射

类/属性 属性 在 XML 中的结果
[XmlRoot("Persons")]
类 UserList
<Persons>...</Persons>
List<User> Users
[XmlElement("Person")]
<Person>...</Person>
int Id
[XmlAttribute("id")]
id="..." (属性)
string Name
[XmlElement("FullName")]
<FullName>...</FullName>
string[] Emails
[XmlArray("EMails")][XmlArrayItem("Email")]
<EMails><Email>...</Email></EMails>
DateTime RegisteredAt
[XmlIgnore] (参见 RegisteredAtString) -
string RegisteredAtString
[XmlAttribute("registered")]
registered="..."
Address[] Addresses
[XmlArray("UserAddresses")][XmlArrayItem("Address")]
<UserAddresses>...</UserAddresses>

2. XML 序列化主要属性概览

我们来看看那些最常用的属性,.NET 用户们这几年都靠它们把类装扮得美美的。

属性 [XmlElement] — 元素名称

改变 XML 中元素的名字,或者把属性/字段标记为 XML 元素。

public class Person
{
    [XmlElement("FullName")]
    public string Name { get; set; }
}

XML:

<Person>
  <FullName>瓦西里·彼得罗夫</FullName>
</Person>

如果不指定属性 —— 默认情况下元素名会与属性/字段名相同。

属性 [XmlAttribute] — 序列化为 XML 属性

把属性/字段序列化成 XML 的属性(attribute),而不是子元素。

public class Person
{
    [XmlAttribute("id")]
    public int Id { get; set; }
}

XML:

<Person id="123"></Person>

在真实的 XML API 里,常把 id、日期、标志之类的数据放在属性里。

属性 [XmlIgnore] — 跳过字段/属性

这是偏执狂的梦想:把某个属性从序列化和反序列化流程里彻底剔除!所有标记了这个属性的成员,都不会出现在最终的 XML 里。

public class Person
{
    [XmlIgnore]
    public string InternalNote { get; set; }
}

XML:(InternalNote 不会出现在这里,也不会出现)

属性 [XmlArray] 和 [XmlArrayItem] — 控制集合

专用于数组和集合的属性。[XmlArray] 指定外层包装的名字,[XmlArrayItem] 指定单个项目的名字。

public class Person
{
    [XmlArray("Phones")]
    [XmlArrayItem("Phone")]
    public string[] PhoneNumbers { get; set; }
}

XML:

<Person>
  <Phones>
    <Phone>+12951234567</Phone>
    <Phone>+12876543210</Phone>
  </Phones>
</Person>

属性 [XmlRoot] — 根元素名称

标记类本身以改变根元素的名字。

[XmlRoot("User")]
public class Person
{
    public string Name { get; set; }
}

XML:

<User>
  <Name>安娜</Name>
</User>

属性 [XmlText] — 序列化为元素的文本内容

有时候(或按规范要求),你希望某个属性的值成为元素的纯文本内容,而不是它的子元素或属性。

public class Note
{
    [XmlText]
    public string Content { get; set; }
}

XML:

<Note>给奶奶打电话!</Note>

属性 [XmlNamespaceDeclarations], [XmlElement(Type = ...)]

对于更高级的场景(比如带命名空间的序列化、支持继承等),还有更多专用属性。强烈建议看看 官方关于 XML 序列化的文档 来获取完整列表。

3. 实战:在真实例子里改进序列化

我们一步步把教学用的小应用升级成“XML 序列化的高级版”,让输出能让最挑剔的 XML 检查器也点个赞。

没有属性的基础类

假设我们有这样一个用户类:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string[] Emails { get; set; }
    public DateTime RegisteredAt { get; set; }
    public string Password { get; set; }
}

默认序列化大概会生成这样的 XML(日期也会用默认格式):

<User>
  <Id>42</Id>
  <Name>Ivan Ivanov</Name>
  <Emails>
    <string>user@mail.com</string>
    <string>admin@site.org</string>
  </Emails>
  <RegisteredAt>2024-07-01T00:00:00</RegisteredAt>
  <Password>qwerty</Password>
</User>

问题在哪里:

  • 密码最好根本不要序列化。
  • 希望把日期做成属性并用可读的格式。
  • 希望 Emails 的格式更漂亮。
  • 希望根元素是 <Person>。

通过属性做配置

[XmlRoot("Person")]
public class User
{
    [XmlAttribute("id")]
    public int Id { get; set; }

    [XmlElement("FullName")]
    public string Name { get; set; }

    [XmlArray("EMails")]
    [XmlArrayItem("Email")]
    public string[] Emails { get; set; }

    [XmlAttribute("registered")]
    public string RegisteredAtString
    {
        get => RegisteredAt.ToString("yyyy-MM-dd");
        set => RegisteredAt = DateTime.Parse(value);
    }

    [XmlIgnore]
    public string Password { get; set; }

    [XmlIgnore]
    public DateTime RegisteredAt { get; set; }
}

细节说明:

  • 用 [XmlAttribute("registered")] 把日期序列化为属性。
  • 为了不是直接序列化 DateTime,而是序列化它的字符串表示,我们添加了单独的属性 RegisteredAtString,并用 [XmlIgnore] 隐藏真实的字段。
  • 用 [XmlIgnore] 隐藏密码。
  • Emails 数组配置后,外层标签是 <EMails>,每个 email 是 <Email>。

结果:

<Person id="42" registered="2024-07-01">
  <FullName>Ivan Ivanov</FullName>
  <EMails>
    <Email>user@mail.com</Email>
    <Email>admin@site.org</Email>
  </EMails>
</Person>

4. 嵌套对象的处理

当有嵌套对象(比如用户和他的地址)时,XML 的结构能很好地表现层级关系。

public class Address
{
    [XmlAttribute]
    public string City { get; set; }

    [XmlText]
    public string Details { get; set; }
}

public class User
{
    // ...其他属性,见上文...

    public Address[] Addresses { get; set; }
}

再为地址数组加上配置:

[XmlArray("UserAddresses")]
[XmlArrayItem("Address")]
public Address[] Addresses { get; set; }

那么 XML 会像这样:

<Person id="42" registered="2024-07-01">
  <FullName>Ivan Ivanov</FullName>
  <EMails>
    <Email>user@mail.com</Email>
  </EMails>
  <UserAddresses>
    <Address City="Neon city">扎姆科瓦娅街, 1号</Address>
    <Address City="North Cave">克赖尼亚街, 12号</Address>
  </UserAddresses>
</Person>

可以看到每个 <Address> 的 city 是属性,而地址本身作为元素内部的文本给出。

5. 常见错误和注意事项

如果你给复杂对象加了配置但在 XML 里看不到预期的变化——很可能是忘了把属性正确地标上。有时候属性或字段没有被标注,.NET 就会回退到默认行为。

另一种常见陷阱是集合的名字。如果集合(比如字符串数组)既没有标记 [XmlArray],也没有标记 [XmlArrayItem],那么元素会用 string 作为标签(就像前面的例子)。为了避免这种情况,最好总是显式指定标签名。

对只有 getter 的属性(只读属性)要小心 —— 这类属性默认是不能反序列化的。尽量提供 public 的 set。

有些类型(比如字典或接口)不能被标准的 XmlSerializer 直接序列化,或者需要额外处理。

2
任务
C# SELF, 第 48 级, 课程 1
已锁定
使用 XmlArray 和 XmlArrayItem 序列化集合
使用 XmlArray 和 XmlArrayItem 序列化集合
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION