C#序列化与反序列化详细讲解与应用

序列化与反序列化是 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. 通用约束(所有序列化方式都需注意)

  1. 要序列化的类建议是**普通实体类(POCO)**,包含属性、无复杂业务逻辑;
  2. 避免序列化敏感数据(如密码),若必须序列化需先加密;
  3. 反序列化不可信数据(如网络接收的 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>
关键注意事项
  1. 类必须有无参构造函数 (显式 / 隐式均可),否则抛出InvalidOperationException
  2. 仅序列化公共属性 / 字段,私有成员不会被序列化;
  3. [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(未序列化)
        }
    }
}
关键注意事项
  1. 类必须标记 **[Serializable]** 特性,否则抛出SerializationException
  2. 用 **[NonSerialized]标记无需序列化的字段 **(仅作用于字段,若要忽略属性,需将属性的底层字段标记);
  3. 严禁反序列化不可信数据(如网络接收的二进制流),会导致代码注入、系统被攻击;
  4. .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. 最佳实践总结

  1. 格式选择 :90% 的场景优先用System.Text.Json ,复杂需求用Newtonsoft.Json,传统系统用 XML,仅.NET 内部信任场景用二进制(尽量避免);
  2. 实体设计:使用**DTO(数据传输对象)**,仅包含序列化所需字段,避免序列化大对象 / 无关字段;
  3. 性能优化 :生产环境关闭 JSON 格式化(WriteIndented = false)、忽略 null 值,大对象序列化使用流式处理(Stream)而非一次性加载到内存;
  4. 安全防护 :① 不反序列化不可信数据;② 敏感字段用[JsonIgnore]/[XmlIgnore]忽略,若必须传输需加密;③ 反序列化后校验数据合法性(如字段范围、类型);
  5. 异常处理:序列化 / 反序列化时必须加 try-catch,文件 / 网络序列化失败时返回默认值,保证程序健壮性。

七、核心知识点总结

  1. 核心本质 :序列化是对象→可存储 / 传输格式 ,反序列化是格式→对象,解决数据持久化和跨系统传输问题;
  2. 主流实现:JSON(System.Text.Json/Newtonsoft.Json)是开发首选,XML 适用于传统场景,二进制仅适用于.NET 内部信任场景;
  3. 关键注解[JsonIgnore]/[XmlIgnore]忽略字段、[JsonPropertyName]映射 JSON 字段、[Serializable]二进制序列化标记;
  4. 实战核心:配置持久化、API 数据传输、Redis 缓存、对象深拷贝是序列化的四大高频应用,需封装工具类提升复用性;
  5. 避坑重点:保证无参构造、处理循环引用、避免中文转义、做好异常处理和安全防护。
相关推荐
mudtools1 天前
搭建一套.net下能落地的飞书考勤系统
后端·c#·.net
玩泥巴的2 天前
搭建一套.net下能落地的飞书考勤系统
c#·.net·二次开发·飞书
唐宋元明清21882 天前
.NET 本地Db数据库-技术方案选型
windows·c#
lindexi2 天前
dotnet DirectX 通过可等待交换链降低输入渲染延迟
c#·directx·d2d·direct2d·vortice
qq_454245032 天前
基于组件与行为的树状节点系统
数据结构·c#
bugcome_com2 天前
C# 类的基础与进阶概念详解
c#
雪人不是菜鸡2 天前
简单工厂模式
开发语言·算法·c#
铸人2 天前
大数分解的Shor算法-C#
开发语言·算法·c#
未来之窗软件服务2 天前
AI人工智能(二十四)错误示范ASR张量错误C#—东方仙盟练气期
开发语言·人工智能·c#·仙盟创梦ide·东方仙盟
yong99902 天前
基于C#实现的UPnP端口映射程序
开发语言·c#