C# 序列化技术全面解析:原理、实现与应用场景
在软件开发中,数据持久化和网络通信是两个至关重要的环节。想象一下,当我们需要将一个复杂的对象保存到文件中,或者通过网络发送到另一台计算机时,如何有效地表示这个对象?这就是序列化技术要解决的问题。序列化(Serialization)是将对象转换为可存储或传输的格式的过程,而反序列化(Deserialization)则是将这些数据重新转换为对象的过程。
C# 作为一门成熟的面向对象编程语言,提供了丰富多样的序列化解决方案。本文将全面探讨 C# 中的各种序列化技术,包括二进制序列化、XML 序列化、JSON 序列化以及数据契约序列化,分析它们的原理、实现方式、优缺点以及适用场景,并通过实际代码示例展示如何在项目中应用这些技术。
一、序列化基础概念
1.1 什么是序列化
序列化是指将对象的状态信息转换为可以存储或传输的形式的过程。在序列化过程中,对象将其当前状态(通常是其成员变量的值)写入到临时或持久性存储区。之后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
1.2 为什么需要序列化
序列化技术主要解决以下几个问题:
-
数据持久化:将对象状态保存到文件或数据库中,以便后续重新加载
-
远程通信:在不同应用程序域或网络中的计算机之间传输对象
-
进程间通信:在不同进程之间传递复杂数据结构
-
缓存机制:将对象缓存到内存或分布式缓存中
1.3 C# 中的序列化分类
C# 提供了多种序列化方式,主要可以分为以下几类:
-
二进制序列化
-
XML 序列化
-
JSON 序列化
-
数据契约序列化
-
自定义序列化
每种方式都有其特点和适用场景,开发者需要根据具体需求选择合适的序列化方式。
二、二进制序列化
2.1 二进制序列化概述
二进制序列化是将对象转换为二进制格式的过程,这种格式通常非常紧凑且处理速度快。在 .NET Framework 中,可以使用 BinaryFormatter
类来实现二进制序列化。
2.2 实现二进制序列化
下面是一个完整的二进制序列化示例:
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;[Serializable]
public class Person
{public string Name { get; set; }public int Age { get; set; }[NonSerialized]public string TemporaryData; // 这个字段不会被序列化
}public class BinarySerializationDemo
{public static void Demo(){Person person = new Person { Name = "张三", Age = 30, TemporaryData = "临时数据" };// 序列化byte[] serializedData;using (MemoryStream stream = new MemoryStream()){BinaryFormatter formatter = new BinaryFormatter();formatter.Serialize(stream, person);serializedData = stream.ToArray();}Console.WriteLine($"序列化后的字节数: {serializedData.Length}");// 反序列化using (MemoryStream stream = new MemoryStream(serializedData)){BinaryFormatter formatter = new BinaryFormatter();Person deserializedPerson = (Person)formatter.Deserialize(stream);Console.WriteLine($"姓名: {deserializedPerson.Name}");Console.WriteLine($"年龄: {deserializedPerson.Age}");Console.WriteLine($"临时数据: {deserializedPerson.TemporaryData ?? "null"}");}}
}
2.3 二进制序列化的特点
优点:
-
序列化后的数据非常紧凑,占用空间小
-
序列化和反序列化速度快
-
可以完整保留对象图和类型信息
缺点:
-
数据不可读,难以调试
-
平台依赖性较强
-
在 .NET Core/.NET 5+ 中被认为不安全,已不建议使用
2.4 安全注意事项
由于 BinaryFormatter
存在严重的安全风险,微软已从 .NET Core 开始不推荐使用它。攻击者可能利用它执行任意代码。如果确实需要使用二进制序列化,可以考虑以下替代方案:
-
使用
System.Text.Json
或Newtonsoft.Json
进行 JSON 序列化 -
使用
XmlSerializer
进行 XML 序列化 -
实现自定义的二进制序列化
三、XML 序列化
3.1 XML 序列化概述
XML 序列化将对象的公共属性和字段转换为 XML 格式。与二进制序列化不同,XML 序列化不包含类型信息,只序列化公共属性和字段。
3.2 实现 XML 序列化
using System;
using System.IO;
using System.Xml.Serialization;[Serializable]
public class Product
{public int Id { get; set; }public string Name { get; set; }public decimal Price { get; set; }[XmlIgnore]public DateTime CreatedAt { get; set; } // 这个属性不会被序列化
}public class XmlSerializationDemo
{public static void Demo(){Product product = new Product { Id = 1001, Name = "笔记本电脑", Price = 5999.99m,CreatedAt = DateTime.Now};// 序列化XmlSerializer serializer = new XmlSerializer(typeof(Product));string xmlString;using (StringWriter writer = new StringWriter()){serializer.Serialize(writer, product);xmlString = writer.ToString();}Console.WriteLine("序列化的XML:");Console.WriteLine(xmlString);// 反序列化using (StringReader reader = new StringReader(xmlString)){Product deserializedProduct = (Product)serializer.Deserialize(reader);Console.WriteLine($"\n反序列化结果:");Console.WriteLine($"ID: {deserializedProduct.Id}");Console.WriteLine($"名称: {deserializedProduct.Name}");Console.WriteLine($"价格: {deserializedProduct.Price}");Console.WriteLine($"创建时间: {deserializedProduct.CreatedAt}");}}
}
3.3 XML 序列化的特点
优点:
-
生成的 XML 可读性强
-
跨平台兼容性好
-
可以通过 XSD 验证数据格式
-
支持通过属性精细控制序列化过程
缺点:
-
数据冗余多,文件体积较大
-
序列化和反序列化性能较低
-
只能序列化公共成员
3.4 XML 序列化高级控制
通过使用各种 XML 特性,可以精细控制序列化过程:
[XmlRoot("ProductItem")]
public class AdvancedProduct
{[XmlAttribute("productId")]public int Id { get; set; }[XmlElement("productName")]public string Name { get; set; }[XmlElement("productPrice")]public decimal Price { get; set; }[XmlElement("isAvailable")]public bool IsAvailable { get; set; }[XmlArray("Categories")][XmlArrayItem("Category")]public List<string> Categories { get; set; }
}
四、JSON 序列化
4.1 JSON 序列化概述
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,也易于机器解析和生成。在 Web 开发和 API 设计中,JSON 已成为事实上的标准。
C# 中有两种主要的 JSON 序列化实现:
-
System.Text.Json
(.NET Core 3.0 及以后版本内置) -
Newtonsoft.Json
(流行的第三方库)
4.2 使用 System.Text.Json 实现 JSON 序列化
using System;
using System.Text.Json;
using System.Text.Json.Serialization;public class Order
{public int OrderId { get; set; }public string CustomerName { get; set; }[JsonPropertyName("items")]public List<OrderItem> OrderItems { get; set; }[JsonIgnore]public string InternalNote { get; set; }
}public class OrderItem
{public string ProductName { get; set; }public int Quantity { get; set; }public decimal UnitPrice { get; set; }
}public class SystemTextJsonDemo
{public static void Demo(){Order order = new Order{OrderId = 1001,CustomerName = "李四",InternalNote = "重要客户",OrderItems = new List<OrderItem>{new OrderItem { ProductName = "鼠标", Quantity = 2, UnitPrice = 89.50m },new OrderItem { ProductName = "键盘", Quantity = 1, UnitPrice = 199.00m }}};// 序列化选项var options = new JsonSerializerOptions{WriteIndented = true, // 美化输出PropertyNamingPolicy = JsonNamingPolicy.CamelCase // 驼峰命名};// 序列化string jsonString = JsonSerializer.Serialize(order, options);Console.WriteLine("序列化的JSON:");Console.WriteLine(jsonString);// 反序列化Order deserializedOrder = JsonSerializer.Deserialize<Order>(jsonString, options);Console.WriteLine($"\n反序列化结果 - 订单ID: {deserializedOrder.OrderId}");Console.WriteLine($"客户名称: {deserializedOrder.CustomerName}");Console.WriteLine($"订单项数: {deserializedOrder.OrderItems?.Count ?? 0}");}
}
4.3 使用 Newtonsoft.Json 实现 JSON 序列化
using System;
using Newtonsoft.Json;public class NewtonsoftJsonDemo
{public static void Demo(){var data = new{Name = "王五",Age = 35,IsActive = true,LastLogin = DateTime.Now,Scores = new[] { 90, 85, 95 }};// 序列化设置var settings = new JsonSerializerSettings{Formatting = Formatting.Indented,NullValueHandling = NullValueHandling.Ignore,DateFormatString = "yyyy-MM-dd HH:mm:ss"};// 序列化string json = JsonConvert.SerializeObject(data, settings);Console.WriteLine("使用Newtonsoft.Json序列化的JSON:");Console.WriteLine(json);// 反序列化var deserializedData = JsonConvert.DeserializeAnonymousType(json, data, settings);Console.WriteLine($"\n反序列化结果 - 姓名: {deserializedData.Name}");Console.WriteLine($"年龄: {deserializedData.Age}");}
}
4.4 JSON 序列化的特点
优点:
-
数据格式轻量,文件体积小
-
可读性较好
-
广泛支持于各种编程语言和平台
-
在 Web 开发中已成为标准
缺点:
-
相比二进制格式,仍然有一定冗余
-
处理某些复杂类型(如循环引用)需要额外配置
4.5 System.Text.Json vs Newtonsoft.Json
特性 | System.Text.Json | Newtonsoft.Json |
---|---|---|
性能 | 更高 | 较低 |
内存分配 | 更少 | 较多 |
功能丰富度 | 基本功能 | 非常丰富 |
内置支持 | .NET Core 3.0+ | 需要安装NuGet包 |
异步序列化 | 支持 | 不支持 |
处理循环引用 | 有限支持 | 完善支持 |
对于新项目,建议优先使用 System.Text.Json
,除非需要 Newtonsoft.Json
的某些特有功能。
五、数据契约序列化
5.1 数据契约序列化概述
数据契约序列化是 WCF (Windows Communication Foundation) 中使用的一种序列化机制,通过 DataContractSerializer
类实现。它比 XML 序列化更灵活,但需要显式标记要序列化的成员。
5.2 实现数据契约序列化
using System;
using System.IO;
using System.Runtime.Serialization;[DataContract]
public class Employee
{[DataMember(Name = "id")]public int EmployeeId { get; set; }[DataMember]public string Name { get; set; }[DataMember(Order = 3)]public string Department { get; set; }public string SecretCode { get; set; } // 不会被序列化
}public class DataContractSerializationDemo
{public static void Demo(){Employee emp = new Employee{EmployeeId = 1001,Name = "赵六",Department = "研发部",SecretCode = "ABC123"};// 序列化DataContractSerializer serializer = new DataContractSerializer(typeof(Employee));string xmlString;using (var stream = new MemoryStream()){serializer.WriteObject(stream, emp);stream.Position = 0;using (var reader = new StreamReader(stream)){xmlString = reader.ReadToEnd();}}Console.WriteLine("数据契约序列化的XML:");Console.WriteLine(xmlString);// 反序列化using (var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(xmlString))){Employee deserializedEmp = (Employee)serializer.ReadObject(stream);Console.WriteLine($"\n反序列化结果 - ID: {deserializedEmp.EmployeeId}");Console.WriteLine($"姓名: {deserializedEmp.Name}");Console.WriteLine($"部门: {deserializedEmp.Department}");Console.WriteLine($"密码: {deserializedEmp.SecretCode ?? "null"}");}}
}
5.3 数据契约序列化的特点
优点:
-
对序列化过程有更精细的控制
-
支持版本控制,对数据变更更友好
-
可以处理复杂对象图和继承层次结构
-
性能优于 XML 序列化
缺点:
-
需要显式标记要序列化的成员
-
生成的 XML 不如 XmlSerializer 生成的那么简洁
-
主要用于 WCF 服务
六、序列化技术选型指南
在选择序列化技术时,应考虑以下因素:
-
性能需求:二进制 > JSON ≈ 数据契约 > XML
-
数据大小:二进制 < JSON < 数据契约 < XML
-
可读性:XML ≈ JSON > 数据契约 > 二进制
-
跨平台支持:JSON > XML > 数据契约 > 二进制
-
类型安全:二进制 > 数据契约 > XML ≈ JSON
-
安全考虑:JSON ≈ XML > 数据契约 > 二进制
推荐场景:
-
Web API/前后端通信:JSON (System.Text.Json 或 Newtonsoft.Json)
-
配置文件:JSON 或 XML
-
高性能本地存储:考虑自定义二进制格式或 Protocol Buffers
-
WCF 服务:数据契约序列化
-
临时对象存储:二进制序列化(仅限可信环境)
七、高级主题与最佳实践
7.1 处理循环引用
循环引用是指对象之间相互引用形成的环。默认情况下,许多序列化器无法处理这种情况。
在 Newtonsoft.Json 中处理循环引用:
var settings = new JsonSerializerSettings
{PreserveReferencesHandling = PreserveReferencesHandling.Objects,ReferenceLoopHandling = ReferenceLoopHandling.Serialize
};
string json = JsonConvert.SerializeObject(obj, settings);
在 System.Text.Json 中处理循环引用:
var options = new JsonSerializerOptions
{ReferenceHandler = ReferenceHandler.Preserve
};
string json = JsonSerializer.Serialize(obj, options);
7.2 自定义序列化
对于需要特殊处理的类型,可以实现自定义序列化逻辑:
public class CustomObject : IJsonOnSerializing, IJsonOnSerialized, IJsonOnDeserializing, IJsonOnDeserialized
{public string Data { get; set; }void IJsonOnSerializing.OnSerializing(){Console.WriteLine("序列化前调用");}void IJsonOnSerialized.OnSerialized(){Console.WriteLine("序列化后调用");}void IJsonOnDeserializing.OnDeserializing(){Console.WriteLine("反序列化前调用");}void IJsonOnDeserialized.OnDeserialized(){Console.WriteLine("反序列化后调用");}
}
7.3 版本兼容性考虑
在设计可序列化类型时,应考虑未来可能的变更:
-
避免删除已序列化的字段,可以标记为废弃
-
新添加的字段应提供合理的默认值
-
考虑使用
[OptionalField]
特性标记可能不存在的字段 -
实现
ISerializable
接口进行自定义版本控制
7.4 性能优化建议
-
对于频繁序列化的类型,缓存序列化器实例
-
对于大型对象,考虑流式序列化
-
使用
ArrayPool<byte>
减少内存分配 -
在 ASP.NET Core 中,使用
System.Text.Json
源生成器
八、结论
C# 提供了丰富多样的序列化技术,每种技术都有其适用场景。在现代应用开发中,JSON 序列化已成为最常用的方式,特别是对于 Web API 和前后端通信。System.Text.Json
作为 .NET 平台的新标准,提供了高性能和低内存占用的优势,而 Newtonsoft.Json
则因其丰富的功能仍然在许多项目中使用。
对于需要更高性能的场景,可以考虑二进制序列化,但应注意其安全风险。XML 序列化在需要严格模式验证或与旧系统集成的场景中仍然有价值。数据契约序列化则是 WCF 服务开发的首选。
选择序列化技术时,开发者应综合考虑性能需求、数据大小、可读性要求、跨平台需求和安全性等因素。理解各种序列化技术的原理和特点,能够帮助我们在实际项目中做出更合理的技术选型,构建更健壮、高效的应用程序。