1. 介绍
你可能已经知道,.NET 默认会基于 public 字段和属性把对象序列化/反序列化成 XML。但当你想生成漂亮、和别的程序或标准兼容的 XML,或者不想把某些内部细节放进 XML 时,就需要更细的控制。
在 C# 里,这就是靠 序列化属性 来做的——给类、属性或字段打的“标签”。有了这些属性,你几乎能控制一切:哪些成员会出现在 XML、哪些被忽略、元素叫什么名字、是不是变成属性、是否有复杂结构,甚至元素的顺序!
可以把默认序列化想象成自动填表:名字、姓氏、中间名照位置随便填。用属性的话你就像个有权力的文书:决定哪一行写哪儿、跳过哪些、标红哪些、把某些内容藏到机密章下面。
对象与 XML 的映射
| 类/属性 | 属性 | 在 XML 中的结果 |
|---|---|---|
|
类 UserList | |
|
|
|
|
|
id="..." (属性) |
|
|
|
|
|
|
|
[XmlIgnore] (参见 RegisteredAtString) | - |
|
|
|
|
|
|
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 直接序列化,或者需要额外处理。
GO TO FULL VERSION