CodeGym /课程 /C# SELF /读取和写入 XML: XmlSerializer

读取和写入 XML: XmlSerializer

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

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 —— 所以建议显式指定编码。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION