1. 介绍
我们之前已经练过集合。没有它们编程和生活都不方便。想象你写一天的待办:买菜、打给医生、去取快递。你不会为每件事都准备一个单独的小本子。把所有任务放到一个列表里更简单、更合理。
编程也是一样:当对象变多——用户、订单、消息——我们通常不会为每个对象声明一个变量。相反用集合:列表、字典、集合。这样能方便地存储、遍历和处理整组数据。
现实场景举例:
- 在应用间存储和交换数据:你的书籍集合可以从一个程序迁移到另一个程序。
- 把数据集缓存到磁盘上。
- 通过网络传输数据(frontend ↔ backend)。
- 导入/导出信息(比如你想把书导出成 JSON)。
面试问法:问你「你会怎么序列化并保存一个订单列表?」——如果你能说到集合的序列化而不只是单个对象,会很加分!
2. 集合在序列化时如何工作
JSON 和集合:一段短暂的恋爱史
当你序列化普通对象时,它会变成 JSON 对象,形如 { "field": value }。但如果序列化一个列表或数组,会得到 JSON 数组 [ ... ]。
视觉化:
| C# | JSON |
|---|---|
|
|
|
|
|
|
主要魔法:对集合调用 JsonSerializer.Serialize() —— 它会自动把集合变成数组!反过来用 Deserialize<List<T>>() —— 魔法又回来了。
C# 集合与 JSON 数组的对应关系
| 在 C# 中的集合类型 | 示例 | JSON |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3. 示例:书籍数组的序列化与反序列化
我们从最小的例子开始。假设有我们之前用过的 Book 类:
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
}
现在我们创建一个书籍数组,把它序列化,保存到文件,然后再读回来。
using System;
using System.IO;
using System.Text.Json;
namespace LibraryApp
{
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
}
class Program
{
static void Main()
{
// 创建一个书籍数组
Book[] books = new Book[]
{
new Book { Title = "三位好友", Author = "埃里希·玛利亚·雷马克" },
new Book { Title = "大师与玛格丽特", Author = "米哈伊尔·布尔加科夫" },
new Book { Title = "1984", Author = "乔治·奥威尔" }
};
// 把书籍数组序列化成字符串
var options = new JsonSerializerOptions { WriteIndented = true };
string json = JsonSerializer.Serialize(books, options);
Console.WriteLine("JSON 的书籍数组:\\n" + json);
// 同步写入文件
File.WriteAllText("books.json", json);
// 同步从文件读取
string jsonFromFile = File.ReadAllText("books.json");
// 反序列化回数组
Book[]? booksFromFile = JsonSerializer.Deserialize<Book[]>(jsonFromFile);
// 在控制台输出结果
Console.WriteLine("\\n反序列化得到的书籍:");
if (booksFromFile != null)
{
foreach (var book in booksFromFile)
{
Console.WriteLine($"- {book.Title} (作者: {book.Author})");
}
}
}
}
}
这里发生了什么?
- 我们创建了一个 Book[] 数组,填入三本书。
- 通过 JsonSerializer.Serialize 把数组变成漂亮的 JSON 字符串(选项 WriteIndented 使其易读)。
- 把字符串写入文件,再读回来并反序列化 —— 现在我们又得到了书籍数组!
- 检查所有数据是否正确恢复。
确认一下文件 "books.json" 出现在程序目录下。打开它,你会看到类似这样的 JSON:
[
{
"Title": "三位好友",
"Author": "埃里希·玛利亚·雷马克"
},
{
"Title": "大师与玛格丽特",
"Author": "米哈伊尔·布尔加科夫"
},
{
"Title": "1984",
"Author": "乔治·奥威尔"
}
]
4. 示例:序列化集合 List<T>
对 List<Book> 的处理没有区别。序列化器看到集合就把它变成 JSON 数组。
List<Book> myBooks = new List<Book>
{
new Book { Title = "罪与罚", Author = "陀思妥耶夫斯基" },
new Book { Title = "战争与和平", Author = "列夫·托尔斯泰" }
};
string jsonList = JsonSerializer.Serialize(myBooks, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(jsonList);
// 反序列化如下:
List<Book>? loadedBooks = JsonSerializer.Deserialize<List<Book>>(jsonList);
// 检查数据都恢复了!
foreach (var book in loadedBooks!)
{
Console.WriteLine($"{book.Title} ({book.Author})");
}
可以序列化一个整数列表吗?
当然可以!序列化不仅适用于你的类,也适用于简单类型:
List<int> numbers = new List<int> { 10, 20, 30, 40 };
string jsonNums = JsonSerializer.Serialize(numbers); // 结果: [10,20,30,40]
List<int>? loadedNums = JsonSerializer.Deserialize<List<int>>(jsonNums);
// loadedNums: 包含相同值的 List<int>
5. 架构和类比:序列化器如何看待集合?
为了更好理解集合的序列化,看看这张简单的流程图:
graph TD;
A[List[Book] 在 C#] -->|Serialize| B[JSON 数组字符串]
B -->|Write| C[文件 books.json]
C -->|Read| D[从文件读取的 JSON 字符串]
D -->|Deserialize| E[List[Book] 在 C#]
简短描述流程:
- 集合或数组会被序列化成 JSON 中的数组([ ... ])。
- 元素顺序会被保留(除非你用特殊设置改变它)。
- 集合中的每个元素会被单独序列化成对象。
6. 集合序列化的特性:注意点
1. null 与空集合
如果集合是 null,序列化器默认会把 null 写入 JSON。注意在业务逻辑中空集合和 null 可能并不相同!
如果集合是空的(new List<Book>()),JSON 会是 [] —— 空数组。这在你想明确表示没有元素时很方便。
2. 反序列化——顺序很重要
数组中元素的顺序总会被保留。也就是说如果你按某个顺序序列化三本书,反序列化后顺序也是一样的。
3. 集合里放不同类型的对象?
System.Text.Json 默认不支持多态。也就是说如果你有一个 List<Animal>,里面既有 Dog 又有 Cat(它们是派生类),默认情况下无法恢复出每个元素具体的派生类型。多态序列化我们以后会讲,但在普通列表里通常不需要额外处理。
7. 常见错误和误解
错误 №1:序列化集合时忘了集合里可能有私有字段的对象
JsonSerializer 只会序列化公共属性(并且要有 getter/setter)。比如如果你的类是这样的:
public class User
{
public string Login { get; set; }
private string Password { get; set; } // 不会被序列化!
}
密码不会出现在 JSON 中,这对安全来说是好事。但如果你确实想保存它——别忘了把属性设为 public。
错误 №2:序列化包含 null 元素的集合
如果集合里有 null,比如:new List<Book> { null, book2 },序列化后第一个元素位置会是 null。反序列化时也是如此!示例 JSON:
[null, { "Title": "战争与和平", "Author": "列夫·托尔斯泰" }]
实际应用中这种情况不常见,但如果你的集合可能有“空洞”,在逻辑里要考虑到这一点。
错误 №3:反序列化成了错误的类型
常见笔误:你序列化了列表,但反序列化时用了数组(或反过来)。如果类型兼容(Book[] ↔ List<Book>)通常没问题,但有时候会出错,比如你想把字符串数组反序列化成对象而不是集合。
GO TO FULL VERSION