序列化与反序列化是 C# 开发中数据持久化、跨系统传输的核心技术,简单说就是**将内存中的对象转换成可存储 / 传输的格式(序列化)**,再 ** 将该格式还原为原对象(反序列化)** 的过程。
本文会从核心概念→3 种主流实现(附可运行代码)→实战应用场景→避坑技巧逐层讲解,全程贴合实际开发,新手也能直接落地使用。
一、核心概念与基础认知
1. 为什么需要序列化?
内存中的对象是临时存在的,程序关闭 / 进程结束后就会消失,而开发中经常需要:
- 把用户配置、本地数据存到文件 / 数据库(持久化);
- 前后端 / 微服务之间通过接口传输数据(跨系统);
- 将查询结果存入 Redis 等缓存(减少数据库压力)。
这些场景都需要把内存对象 转换成字符串、二进制流等通用格式,序列化就是解决这个问题的核心手段。
2. 反序列化的作用
是序列化的逆操作,将传输 / 存储的通用格式数据,还原成 C# 中可以直接使用的对象,避免手动解析字符串 / 二进制流的繁琐操作。
3. C# 中 3 种主流序列化格式对比
开发中最常用的是JSON、XML、二进制,三者各有适用场景,直接看对比表(重点记 JSON 的优势):
表格
| 序列化方式 | 核心特点 | 可读性 | 跨平台 / 跨语言 | 性能 / 体积 | 适用场景 |
|---|---|---|---|---|---|
| JSON(推荐) | .NET Core3.0 + 内置System.Text.Json,轻量通用 |
高(人类可读) | 极佳(前后端 / 跨语言通用) | 性能高、体积小 | 接口传输、缓存存储、本地轻量配置(90% 的开发场景) |
| XML | 内置XmlSerializer,标签化格式 |
高 | 较好 | 性能一般、体积大 | 传统系统交互、配置文件(如 app.config) |
| 二进制 | 内置BinaryFormatter,字节流格式 |
无(不可读) | 差(仅.NET 体系兼容) | 性能最高、体积最小 | .NET 内部信任系统通信(.NET5 + 已标记过时,有安全风险) |
4. 通用约束(所有序列化方式都需注意)
- 要序列化的类建议是**普通实体类(POCO)**,包含属性、无复杂业务逻辑;
- 避免序列化敏感数据(如密码),若必须序列化需先加密;
- 反序列化不可信数据(如网络接收的 JSON/XML)时,必须做数据校验,防止注入攻击。
二、3 种主流序列化的完整实现(附可运行代码)
以下所有示例均基于 **.NET 6/7/8**(主流框架),代码可直接复制到控制台项目运行,关键步骤带详细注释。
前置准备:定义通用测试实体
所有示例都会使用这个用户信息实体,包含普通属性、特殊字段(如时间)、需要忽略的属性,贴合实际开发:
csharp
/// <summary>
/// 测试用实体:用户信息
/// </summary>
public class User
{
public int Id { get; set; } // 主键
public string UserName { get; set; } // 用户名
public int Age { get; set; } // 年龄
public DateTime CreateTime { get; set; } // 创建时间
public string? Password { get; set; } // 密码(敏感信息,需忽略)
public string? Remark { get; set; } // 备注(可能为null,可忽略)
}
二、JSON 序列化(System.Text.Json):开发首选
.NET Core3.0 + 内置System.Text.Json,无需第三方库、性能高、跨平台 ,是目前 C# 开发的默认首选,覆盖 90% 的序列化场景。
1. 基础序列化 / 反序列化
核心方法:
- 序列化:
JsonSerializer.Serialize<T>(对象, 配置选项)→ 转 JSON 字符串 - 反序列化:
JsonSerializer.Deserialize<T>(JSON字符串, 配置选项)→ 转原对象
完整代码
csharp
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
class JsonBasicDemo
{
static void Main()
{
// 1. 初始化测试对象
var user = new User
{
Id = 1,
UserName = "张三",
Age = 25,
CreateTime = DateTime.Now,
Password = "123456", // 敏感信息,后续忽略
Remark = null // 空值,后续忽略
};
// 2. 配置序列化选项(开发必备,解决中文转义、空值、格式化等问题)
var jsonOptions = new JsonSerializerOptions
{
WriteIndented = true, // 格式化输出(调试用true,生产用false减小体积)
// 解决中文转义问题(默认会把中文转成\uXXXX)
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
// 全局忽略null值属性(避免JSON中有多余的null字段)
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
// 时间类型统一格式(可选,默认是ISO格式)
Converters = { new JsonStringEnumConverter(), new CustomDateTimeConverter() }
};
// 3. 序列化:对象 → JSON字符串
string jsonStr = JsonSerializer.Serialize(user, jsonOptions);
Console.WriteLine("【序列化结果-JSON】");
Console.WriteLine(jsonStr);
// 4. 反序列化:JSON字符串 → 对象
User? desUser = JsonSerializer.Deserialize<User>(jsonStr, jsonOptions);
if (desUser != null)
{
Console.WriteLine("\n【反序列化结果】");
Console.WriteLine($"用户名:{desUser.UserName},年龄:{desUser.Age},创建时间:{desUser.CreateTime:yyyy-MM-dd HH:mm:ss}");
}
}
// 自定义时间转换器(解决时间格式不统一问题,开发常用)
public class CustomDateTimeConverter : JsonConverter<DateTime>
{
private readonly string _format = "yyyy-MM-dd HH:mm:ss"; // 自定义时间格式
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return DateTime.TryParse(reader.GetString(), out var time) ? time : DateTime.Now;
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(_format));
}
}
}
关键特性与注解
表格
| 注解 / 配置 | 作用 | 适用场景 |
|---|---|---|
[JsonPropertyName("user_name")] |
自定义 JSON 字段名(解决前后端命名规范不一致,如前端下划线、后端驼峰) | API 接口传输 |
[JsonIgnore] |
强制忽略某个属性 | 敏感字段(密码)、临时字段 |
[JsonIgnore(Condition=WhenWritingNull)] |
仅当属性为 null 时忽略 | 可选填的备注、扩展字段 |
WriteIndented = false |
关闭格式化,JSON 为一行 | 生产环境(减小传输体积) |
2. 进阶:使用 Newtonsoft.Json(Json.NET)
若System.Text.Json无法满足复杂需求(如复杂循环引用、旧框架兼容),可使用Newtonsoft.Json(业界最成熟的 JSON 库),需先通过 NuGet 安装:
bash
Install-Package Newtonsoft.Json
核心代码示例
csharp
using Newtonsoft.Json;
class NewtonsoftJsonDemo
{
static void Main()
{
var user = new User { Id = 1, UserName = "李四", Age = 30, CreateTime = DateTime.Now };
// 序列化:自定义配置更丰富
string jsonStr = JsonConvert.SerializeObject(user, new JsonSerializerSettings
{
Formatting = Formatting.Indented, // 格式化
DateFormatString = "yyyy-MM-dd HH:mm:ss", // 直接指定时间格式
NullValueHandling = NullValueHandling.Ignore, // 忽略null值
ReferenceLoopHandling = ReferenceLoopHandling.Ignore // 处理循环引用(开发高频需求)
});
// 反序列化
User? desUser = JsonConvert.DeserializeObject<User>(jsonStr);
}
}
核心优势:内置循环引用处理、更灵活的时间格式化、兼容所有.NET 框架(包括.NET Framework)。
三、XML 序列化(XmlSerializer):传统场景适用
XML 是标签化的结构化格式 ,可读性强但体积较大,主要用于传统系统交互、老项目配置文件 ,内置XmlSerializer无需额外安装,核心特点是无需标记 [Serializable],但必须有无参构造函数。
完整实现代码
csharp
using System;
using System.IO;
using System.Xml.Serialization;
// 注意:XML序列化的类,建议显式声明无参构造函数
public class User
{
public User() { } // 无参构造(必须,否则反序列化失败)
public int Id { get; set; }
public string UserName { get; set; } = string.Empty;
public int Age { get; set; }
[XmlAttribute("create_time")] // 转为XML节点的属性(而非子节点)
public DateTime CreateTime { get; set; }
[XmlIgnore] // 忽略该属性,不序列化
public string? Password { get; set; }
}
class XmlSerializationDemo
{
static void Main()
{
var user = new User { Id = 1, UserName = "张三", Age = 25, CreateTime = DateTime.Now };
// 1. 序列化:对象 → XML字符串
XmlSerializer serializer = new XmlSerializer(typeof(User)); // 指定序列化类型
string xmlStr;
using (var sw = new StringWriter())
{
serializer.Serialize(sw, user);
xmlStr = sw.ToString();
}
Console.WriteLine("【XML序列化结果】");
Console.WriteLine(xmlStr);
// 2. 反序列化:XML字符串 → 对象
User? desUser;
using (var sr = new StringReader(xmlStr))
{
desUser = (User)serializer.Deserialize(sr)!;
}
if (desUser != null)
{
Console.WriteLine($"\n反序列化结果:{desUser.UserName},{desUser.Age}岁");
}
}
}
输出结果(XML 格式)
xml
<?xml version="1.0" encoding="utf-16"?>
<User xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" create_time="2026-02-07T10:00:00">
<Id>1</Id>
<UserName>张三</UserName>
<Age>25</Age>
</User>
关键注意事项
- 类必须有无参构造函数 (显式 / 隐式均可),否则抛出
InvalidOperationException; - 仅序列化公共属性 / 字段,私有成员不会被序列化;
- 用
[XmlAttribute]将属性转为 XML 节点的属性(减小 XML 体积),[XmlIgnore]忽略无需序列化的字段。
四、二进制序列化(BinaryFormatter):仅内部信任场景
二进制序列化将对象转为二进制字节流 ,性能最高、体积最小,但不可读、跨平台差 ,且.NET5 + 已标记过时(Obsolete)(存在严重的反序列化注入安全风险),仅适用于.NET 内部完全信任的场景(如老项目内部数据传输)。
完整实现代码
csharp
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
// 注意:二进制序列化必须给类标记[Serializable]特性
[Serializable]
public class User
{
public int Id { get; set; }
public string UserName { get; set; } = string.Empty;
public int Age { get; set; }
public DateTime CreateTime { get; set; }
[NonSerialized] // 标记该字段不序列化(仅作用于字段,不作用于属性)
private string? _password;
public string? Password { get => _password; set => _password = value; }
}
class BinarySerializationDemo
{
static void Main()
{
var user = new User { Id = 1, UserName = "张三", Age = 25, CreateTime = DateTime.Now, Password = "123456" };
// 1. 序列化:对象 → 二进制字节数组
byte[] binaryData;
using (var ms = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(ms, user); // 执行序列化
binaryData = ms.ToArray(); // 转为字节数组
}
Console.WriteLine($"二进制序列化后字节长度:{binaryData.Length}");
// 2. 反序列化:二进制字节数组 → 对象
User? desUser;
using (var ms = new MemoryStream(binaryData))
{
BinaryFormatter formatter = new BinaryFormatter();
desUser = (User)formatter.Deserialize(ms)!;
}
if (desUser != null)
{
Console.WriteLine($"反序列化结果:{desUser.UserName},密码:{desUser.Password}"); // 密码为null(未序列化)
}
}
}
关键注意事项
- 类必须标记 **[Serializable]** 特性,否则抛出
SerializationException; - 用 **[NonSerialized]标记无需序列化的字段 **(仅作用于字段,若要忽略属性,需将属性的底层字段标记);
- 严禁反序列化不可信数据(如网络接收的二进制流),会导致代码注入、系统被攻击;
- .NET5 + 替代方案:若需要二进制序列化,可使用Protobuf-net(第三方库,安全、跨平台)。
五、序列化与反序列化的 4 大实战应用场景
掌握基础用法后,重点是落地到实际开发中,以下是 4 个高频应用场景,附核心实现思路和代码片段。
场景 1:本地应用配置持久化(JSON)
WinForm/WPF/ 控制台应用中,将用户配置(如窗口大小、主题、登录状态)序列化到本地 JSON 文件,程序重启后反序列化恢复配置(开发最常用)。
核心实现(配置管理工具类)
csharp
using System;
using System.IO;
using System.Text.Json;
// 配置实体
public class AppConfig
{
public int WindowWidth { get; set; } = 800; // 默认值
public int WindowHeight { get; set; } = 600;
public string Theme { get; set; } = "Light";
public string? LastLoginUser { get; set; }
public bool RememberPassword { get; set; }
}
// 配置管理工具(封装序列化/反序列化,全局调用)
public static class ConfigHelper
{
// 配置文件路径(程序根目录下的appconfig.json)
private static readonly string _configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "appconfig.json");
// 序列化配置
private static readonly JsonSerializerOptions _options = new()
{
WriteIndented = true,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
// 加载配置(反序列化)
public static AppConfig LoadConfig()
{
try
{
if (!File.Exists(_configPath)) return new AppConfig(); // 文件不存在返回默认配置
string json = File.ReadAllText(_configPath);
return JsonSerializer.Deserialize<AppConfig>(json, _options) ?? new AppConfig();
}
catch (Exception)
{
return new AppConfig(); // 配置损坏返回默认配置,保证程序不崩溃
}
}
// 保存配置(序列化)
public static void SaveConfig(AppConfig config)
{
try
{
string json = JsonSerializer.Serialize(config, _options);
File.WriteAllText(_configPath, json);
}
catch (Exception ex)
{
throw new Exception("保存配置失败", ex);
}
}
}
// 调用示例
// var config = ConfigHelper.LoadConfig(); // 加载
// config.Theme = "Dark"; config.WindowWidth = 1024;
// ConfigHelper.SaveConfig(config); // 保存
场景 2:ASP.NET Core API 前后端数据传输(JSON)
ASP.NET Core 默认使用System.Text.Json,自动完成序列化 / 反序列化:前端传 JSON→后端自动反序列化为 DTO 对象,后端返回对象→自动序列化为 JSON 给前端。
核心实现
csharp
using Microsoft.AspNetCore.Mvc;
using System.Text.Json.Serialization;
// 前端请求DTO(数据传输对象,仅包含接口所需字段)
public class LoginRequest
{
[JsonPropertyName("user_name")] // 匹配前端传参名(下划线)
public string UserName { get; set; } = string.Empty;
[JsonPropertyName("password")]
public string Password { get; set; } = string.Empty;
}
// 统一响应格式(前后端约定)
public class ApiResult<T>
{
public int Code { get; set; } = 200; // 200成功,其他失败
public string Message { get; set; } = "操作成功";
public T? Data { get; set; }
}
// API控制器
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
// 登录接口:前端POST JSON → 后端自动反序列化为LoginRequest
[HttpPost("login")]
public IActionResult Login([FromBody] LoginRequest request)
{
// 验证参数
if (string.IsNullOrEmpty(request.UserName) || string.IsNullOrEmpty(request.Password))
{
return Ok(new ApiResult<string> { Code = 400, Message = "用户名或密码不能为空" });
}
// 模拟登录逻辑
if (request.UserName == "admin" && request.Password == "123456")
{
return Ok(new ApiResult<string> { Data = Guid.NewGuid().ToString("N") }); // 返回Token,自动序列化为JSON
}
return Ok(new ApiResult<string> { Code = 500, Message = "用户名或密码错误" });
}
}
场景 3:Redis 缓存存储对象(JSON)
高并发系统中,将数据库查询结果(如用户、商品信息)序列化为 JSON 存入 Redis,避免频繁查询数据库,提升性能(核心:对象→JSON 字符串存 Redis,取出来→反序列化为对象)。
核心实现(Redis 工具类,基于 StackExchange.Redis)
bash
# 先安装NuGet包
Install-Package StackExchange.Redis
Install-Package Newtonsoft.Json
csharp
using StackExchange.Redis;
using Newtonsoft.Json;
using System;
public static class RedisHelper
{
// 静态Redis连接(单例,避免频繁创建连接)
private static readonly ConnectionMultiplexer _redis = ConnectionMultiplexer.Connect("127.0.0.1:6379,defaultDatabase=0");
private static readonly IDatabase _db = _redis.GetDatabase();
// 存入对象:序列化对象为JSON字符串
public static void SetObject<T>(string key, T value, TimeSpan? expiry = null)
{
if (value == null) return;
string json = JsonConvert.SerializeObject(value, new JsonSerializerSettings
{
DateFormatString = "yyyy-MM-dd HH:mm:ss",
NullValueHandling = NullValueHandling.Ignore
});
_db.StringSet(key, json, expiry ?? TimeSpan.FromHours(1)); // 设置过期时间,避免缓存永久有效
}
// 获取对象:反序列化JSON字符串为对象
public static T? GetObject<T>(string key)
{
if (!_db.KeyExists(key)) return default;
string json = _db.StringGet(key);
return JsonConvert.DeserializeObject<T>(json);
}
}
// 调用示例
// var product = dbContext.Products.First(p => p.Id == 1001); // 从数据库查询
// RedisHelper.SetObject($"product_{1001}", product, TimeSpan.FromMinutes(30)); // 存入Redis
// var cacheProduct = RedisHelper.GetObject<Product>($"product_{1001}"); // 从Redis获取,无需查库
场景 4:对象深拷贝(二进制 / JSON)
C# 中普通赋值是浅拷贝 (引用类型共享内存),若要实现深拷贝(完全复制一个新对象,互不影响),可通过序列化 + 反序列化实现(简单高效,无需手动重写 Clone 方法)。
核心实现(JSON 深拷贝,推荐,无安全风险)
csharp
using System.Text.Json;
public static class ObjectExt
{
// 泛型扩展方法:JSON实现深拷贝
public static T DeepClone<T>(this T obj)
{
if (obj == null) return default!;
var options = new JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
string json = JsonSerializer.Serialize(obj, options);
return JsonSerializer.Deserialize<T>(json, options)!;
}
}
// 调用示例
// var user1 = new User { UserName = "张三", Age = 25 };
// var user2 = user1.DeepClone(); // 深拷贝
// user2.UserName = "李四"; // 修改user2,不会影响user1
六、开发避坑与最佳实践
1. 必踩坑点与解决方案
表格
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 反序列化失败:无参构造函数缺失 | XML/JSON 序列化需要无参构造初始化对象 | 给实体类显式声明无参构造函数 |
| 中文转义为 \uXXXX | System.Text.Json 默认对中文转义 | 添加Encoder = UnsafeRelaxedJsonEscaping配置 |
| 循环引用导致序列化死循环 | 实体互相引用(如员工→部门→员工) | 1. System.Text.Json:配置ReferenceHandler = ReferenceHandler.Preserve;2. Newtonsoft.Json:配置ReferenceLoopHandling = Ignore |
| 二进制序列化忽略属性失败 | [NonSerialized] 仅作用于字段,不作用于属性 | 将属性的底层私有字段标记 [NonSerialized] |
| 反序列化后对象属性为默认值 | JSON/XML 字段名与实体属性名不匹配 | 使用[JsonPropertyName]/[XmlAttribute]做字段映射 |
2. 最佳实践总结
- 格式选择 :90% 的场景优先用System.Text.Json ,复杂需求用Newtonsoft.Json,传统系统用 XML,仅.NET 内部信任场景用二进制(尽量避免);
- 实体设计:使用**DTO(数据传输对象)**,仅包含序列化所需字段,避免序列化大对象 / 无关字段;
- 性能优化 :生产环境关闭 JSON 格式化(
WriteIndented = false)、忽略 null 值,大对象序列化使用流式处理(Stream)而非一次性加载到内存; - 安全防护 :① 不反序列化不可信数据;② 敏感字段用
[JsonIgnore]/[XmlIgnore]忽略,若必须传输需加密;③ 反序列化后校验数据合法性(如字段范围、类型); - 异常处理:序列化 / 反序列化时必须加 try-catch,文件 / 网络序列化失败时返回默认值,保证程序健壮性。
七、核心知识点总结
- 核心本质 :序列化是对象→可存储 / 传输格式 ,反序列化是格式→对象,解决数据持久化和跨系统传输问题;
- 主流实现:JSON(System.Text.Json/Newtonsoft.Json)是开发首选,XML 适用于传统场景,二进制仅适用于.NET 内部信任场景;
- 关键注解 :
[JsonIgnore]/[XmlIgnore]忽略字段、[JsonPropertyName]映射 JSON 字段、[Serializable]二进制序列化标记; - 实战核心:配置持久化、API 数据传输、Redis 缓存、对象深拷贝是序列化的四大高频应用,需封装工具类提升复用性;
- 避坑重点:保证无参构造、处理循环引用、避免中文转义、做好异常处理和安全防护。