1. 介绍
XML (eXtensible Markup Language) — 可扩展标记语言,是一种常用的用于存储和交换复杂结构化数据的格式。相比 JSON,XML 并不精简,但在银行业、政务接口、文档和与 1С、SAP、Oracle 等系统的集成中仍然很常用。在 Windows 和 .NET 里有些数据(例如配置)仍然以 XML 存储。
外观上 XML 类似 HTML,但没有像 <body> / <div> 那样固定的标签集合——你可以自己定义元素结构。示例:
<Person>
<FirstName>Ivan</FirstName>
<LastName>Petrov</LastName>
<Age>30</Age>
</Person>
XML 基础及其与 C# 对象的对应
与 JSON 不同,XML 没有专门的数组类型或布尔类型 —— 一切都是元素和属性。列表结构通常通过重复同名元素来表示。
在 C# 中我们一般用序列化/反序列化机制来方便地处理 XML(即用 XmlSerializer)。
序列化 — 把 C# 对象转换成 XML 字符串。 反序列化 — 反过来:从 XML 得到 C# 对象。
如果你的类满足某些要求,这个过程是开箱即可用的 —— 下面会讲清楚这些要求。
为什么使用 XmlSerializer?
XmlSerializer 是 .NET 的标准工具(命名空间 System.Xml.Serialization)用来在对象和 XML 之间转换。它简单、类型安全,适合做数据导入/导出、配置、系统间交换和对接政务接口。
2. 示例:序列化对象
假设我们有一个用户模型:
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
把对象序列化为 XML:
using System.Xml.Serialization;
User user = new User { FirstName = "伊万", LastName = "彼得罗夫", Age = 30 };
// Для сериализации нужен тип
XmlSerializer serializer = new XmlSerializer(typeof(User));
// Записываем XML в файл
using FileStream fs = new FileStream("user.xml", FileMode.Create);
serializer.Serialize(fs, user);
发生了什么?
- 创建了 User 类的一个对象。
- 为类型 User 创建了一个 XmlSerializer 实例。
- 通过 FileStream 打开了文件。
- 调用了 Serialize() 方法,它把对象变成 XML 并写入文件。
文件 user.xml 的内容:
<?xml version="1.0"?>
<User xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FirstName>伊万</FirstName>
<LastName>彼得罗夫</LastName>
<Age>30</Age>
</User>
就这么简单 —— 最少的代码得到一个可用的 XML。
3. 反序列化:把 XML 读回对象
读取刚才的 XML 并恢复 C# 对象:
// Открываем XML-файл
using FileStream fs = new FileStream("user.xml", FileMode.Open);
User? loadedUser = serializer.Deserialize(fs) as User;
Console.WriteLine($"{loadedUser?.FirstName} {loadedUser?.LastName}, 年龄: {loadedUser?.Age}");
方法 Deserialize() 返回 object,所以需要把它转换回 User(这里用 as)。如果文件正确并匹配模型,对象会被完整恢复。
4. 序列化的工作方式:常见特性
XML 序列化要求满足一些规则:
• 用于序列化的类必须有公共的无参构造函数(public,无参数)。
• 可序列化的属性必须是公共的:需要有 getter 和 setter。私有成员会被忽略。
• 只读属性、静态属性和抽象属性不起作用,都会被忽略。
如果违反规则,会抛出 InvalidOperationException,提示类似 "XmlSerializer cannot serialize this type"。
5. 序列化集合:数组和列表
如果要序列化多个用户,通常用一个包装类:
public class UserList
{
public List<User> Users { get; set; } = new List<User>();
}
创建集合并序列化:
UserList users = new UserList
{
Users = new List<User>
{
new User { FirstName = "伊万", LastName = "彼得罗夫", Age = 30 },
new User { FirstName = "玛丽亚", LastName = "伊万诺娃", Age = 25 }
}
};
XmlSerializer serializer = new XmlSerializer(typeof(UserList));
using FileStream fs = new FileStream("users.xml", FileMode.Create);
serializer.Serialize(fs, users);
得到的 XML:
<?xml version="1.0"?>
<UserList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Users>
<User>
<FirstName>伊万</FirstName>
<LastName>彼得罗夫</LastName>
<Age>30</Age>
</User>
<User>
<FirstName>玛丽亚</FirstName>
<LastName>伊万诺娃</LastName>
<Age>25</Age>
</User>
</Users>
</UserList>
能不能直接序列化 List<User>? 能,但根元素名会是 ArrayOfUser,在对接外部系统时有时不太合适。
6. 用属性控制序列化
通过属性可以控制生成的 XML:重命名元素、把属性作为 XML attribute、忽略字段等。常用的属性有: [XmlElement], [XmlAttribute], [XmlArray], [XmlIgnore]。
作为属性序列化
把名和姓作为属性写出,年龄作为元素:
public class User
{
[XmlAttribute]
public string FirstName { get; set; }
[XmlAttribute]
public string LastName { get; set; }
[XmlElement]
public int Age { get; set; }
}
结果:
<User FirstName="伊万" LastName="彼得罗夫">
<Age>30</Age>
</User>
控制元素名字
把标签重命名为更可读的名字:
public class User
{
[XmlElement("名字")]
public string FirstName { get; set; }
[XmlElement("姓氏")]
public string LastName { get; set; }
[XmlElement("年龄")]
public int Age { get; set; }
}
生成的 XML:
<User>
<名字>伊万</名字>
<姓氏>彼得罗夫</姓氏>
<年龄>30</年龄>
</User>
忽略属性
如果某个属性不想序列化,就加上 [XmlIgnore]:
public class User
{
public string FirstName { get; set; }
[XmlIgnore]
public string InternalCode { get; set; }
}
7. 序列化层级和嵌套对象
嵌套类会变成嵌套的 XML 元素:
public class Address
{
public string City { get; set; }
public string Street { get; set; }
}
public class User
{
public string FirstName { get; set; }
public Address Address { get; set; }
}
var user = new User
{
FirstName = "安娜",
Address = new Address { City = "霓虹城", Street = "榆树街" }
};
XmlSerializer serializer = new XmlSerializer(typeof(User));
using var fs = new FileStream("user_with_address.xml", FileMode.Create);
serializer.Serialize(fs, user);
生成的 XML:
<User>
<FirstName>安娜</FirstName>
<Address>
<City>霓虹城</City>
<Street>榆树街</Street>
</Address>
</User>
8. 在内存中序列化/反序列化(不使用文件)
序列化为字符串
using System.Text;
var serializer = new XmlSerializer(typeof(User));
using var stringWriter = new StringWriter();
serializer.Serialize(stringWriter, user);
string xml = stringWriter.ToString();
Console.WriteLine(xml);
从字符串反序列化
var xml = "<User><FirstName>安娜</FirstName></User>";
using var stringReader = new StringReader(xml);
User user = (User)serializer.Deserialize(stringReader);
Console.WriteLine(user.FirstName);
9. 示例:应用数据的导出和导入
把用户列表导出/导入为 XML 文件:
public static void ExportUsers(UserList users, string path)
{
var serializer = new XmlSerializer(typeof(UserList));
using var fs = new FileStream(path, FileMode.Create);
serializer.Serialize(fs, users);
}
public static UserList ImportUsers(string path)
{
var serializer = new XmlSerializer(typeof(UserList));
using var fs = new FileStream(path, FileMode.Open);
return (UserList)serializer.Deserialize(fs);
}
用途:备份、与其他服务交换数据、系统集成。在面试中关于 XmlSerializer 的问题比你想象的要常见——尤其是在对接政务或银行相关业务的公司。
10. 常见错误和注意点
如果字段不是 public,它不会被序列化。
只读属性会被忽略。
属性类型是接口或抽象类的话,序列化器不支持。
类型 DateTime 会以 XSD 格式序列化(参考 文档)。
数组和 List<T> 之类的集合行为是可预测的,但像 ObservableCollection<T> 在某些外部系统那儿可能会有意外。
编码上的坑:如果用 StreamWriter 输出又不显式设置编码,结果可能是 UTF-16;很多系统更期待 UTF-8 —— 所以建议显式指定编码。
GO TO FULL VERSION