C#序列化与反序列化(JSON、XML、二进制)方案
C#序列化与反序列化基础概念
在C#编程领域,序列化是将对象的状态转换为可以存储或传输的形式的过程,而反序列化则是相反的操作,即将存储或传输的数据重新转换回对象。这一机制在很多场景中都至关重要,例如将对象保存到文件中,在网络上传输对象数据,或者在不同的应用程序域之间共享对象状态。
在C#中,常见的序列化格式有JSON、XML和二进制。每种格式都有其特点,适用于不同的场景。JSON以其简洁、轻量的特点,在Web应用和移动应用开发中广泛应用,特别是在前后端数据交互场景。XML则以其良好的结构化和可读性,常用于配置文件以及与传统企业系统交互。二进制格式虽然可读性差,但在性能和空间占用上表现优异,适用于对性能要求极高的内部系统数据传输和存储。
JSON序列化与反序列化
使用System.Text.Json命名空间(.NET 5及更高版本)
从.NET 5开始,微软引入了System.Text.Json
命名空间,提供了高性能的JSON序列化和反序列化功能。
首先,确保项目引用了System.Text.Json
。在一个简单的控制台应用中,我们定义一个类来进行演示:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
接下来进行JSON序列化:
using System.Text.Json;
class Program
{
static void Main()
{
var person = new Person { Name = "Alice", Age = 30 };
string jsonString = JsonSerializer.Serialize(person);
Console.WriteLine(jsonString);
}
}
上述代码通过JsonSerializer.Serialize
方法将Person
对象转换为JSON字符串。输出结果类似:{"Name":"Alice","Age":30}
。
反序列化操作如下:
using System.Text.Json;
class Program
{
static void Main()
{
string jsonString = "{\"Name\":\"Alice\",\"Age\":30}";
Person person = JsonSerializer.Deserialize<Person>(jsonString);
if (person != null)
{
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
}
}
}
这里通过JsonSerializer.Deserialize
方法将JSON字符串转换回Person
对象。
使用Newtonsoft.Json(跨版本兼容性好)
在.NET 5之前,或者在需要更好的跨版本兼容性时,Newtonsoft.Json
是一个非常流行的JSON处理库。
首先通过NuGet安装Newtonsoft.Json
包。同样以Person
类为例,序列化代码如下:
using Newtonsoft.Json;
class Program
{
static void Main()
{
var person = new Person { Name = "Bob", Age = 25 };
string jsonString = JsonConvert.SerializeObject(person);
Console.WriteLine(jsonString);
}
}
反序列化代码如下:
using Newtonsoft.Json;
class Program
{
static void Main()
{
string jsonString = "{\"Name\":\"Bob\",\"Age\":25}";
Person person = JsonConvert.DeserializeObject<Person>(jsonString);
if (person != null)
{
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
}
}
}
Newtonsoft.Json
提供了非常丰富的配置选项,例如可以通过JsonSerializerSettings
来控制序列化和反序列化的行为,比如日期格式、忽略某些属性等。
XML序列化与反序列化
使用System.Xml.Serialization命名空间
在C#中,System.Xml.Serialization
命名空间提供了对XML序列化和反序列化的支持。
对于前面定义的Person
类,要使其可被XML序列化,需要确保类具有公共的无参构造函数(如果自定义了构造函数,必须同时提供无参构造函数):
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person() { }
}
序列化代码如下:
using System.Xml.Serialization;
using System.IO;
class Program
{
static void Main()
{
var person = new Person { Name = "Charlie", Age = 35 };
XmlSerializer serializer = new XmlSerializer(typeof(Person));
using (StreamWriter writer = new StreamWriter("person.xml"))
{
serializer.Serialize(writer, person);
}
}
}
上述代码将Person
对象序列化为XML并保存到person.xml
文件中。生成的XML文件内容类似:
<?xml version="1.0" encoding="utf-8"?>
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Charlie</Name>
<Age>35</Age>
</Person>
反序列化代码如下:
using System.Xml.Serialization;
using System.IO;
class Program
{
static void Main()
{
XmlSerializer serializer = new XmlSerializer(typeof(Person));
using (StreamReader reader = new StreamReader("person.xml"))
{
Person person = (Person)serializer.Deserialize(reader);
if (person != null)
{
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
}
}
}
}
控制XML结构
可以使用属性来控制XML的生成结构。例如,XmlElement
属性可以用于指定元素名称,XmlAttribute
属性用于指定属性。
public class Person
{
[XmlElement("FullName")]
public string Name { get; set; }
[XmlAttribute("PersonAge")]
public int Age { get; set; }
public Person() { }
}
序列化后生成的XML如下:
<?xml version="1.0" encoding="utf-8"?>
<Person PersonAge="40" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FullName>David</FullName>
</Person>
二进制序列化与反序列化
使用System.Runtime.Serialization.Formatters.Binary命名空间
二进制序列化在C#中通过System.Runtime.Serialization.Formatters.Binary
命名空间实现。要进行二进制序列化,类必须标记为[Serializable]
。
[Serializable]
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
序列化代码如下:
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
class Program
{
static void Main()
{
var person = new Person { Name = "Eve", Age = 28 };
BinaryFormatter formatter = new BinaryFormatter();
using (FileStream stream = new FileStream("person.bin", FileMode.Create))
{
formatter.Serialize(stream, person);
}
}
}
上述代码将Person
对象序列化为二进制文件person.bin
。
反序列化代码如下:
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
class Program
{
static void Main()
{
BinaryFormatter formatter = new BinaryFormatter();
using (FileStream stream = new FileStream("person.bin", FileMode.Open))
{
Person person = (Person)formatter.Deserialize(stream);
if (person != null)
{
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
}
}
}
}
注意事项
二进制序列化依赖于类型的完全匹配,包括程序集版本等信息。如果类型定义发生了不兼容的变化,反序列化可能会失败。例如,如果在序列化后给类添加了一个新的非可序列化字段,反序列化可能会出现问题。为了应对这种情况,可以使用[NonSerialized]
属性标记那些不应该被序列化的字段,并且在类型发生变化时,合理处理版本兼容性问题,比如使用序列化代理。
复杂对象的序列化与反序列化
嵌套对象
在实际应用中,对象往往是复杂的,包含嵌套对象。例如,我们定义一个Address
类,并在Person
类中包含Address
对象:
public class Address
{
public string Street { get; set; }
public string City { get; set; }
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Address HomeAddress { get; set; }
public Person() { }
}
对于JSON序列化,无论是System.Text.Json
还是Newtonsoft.Json
,都能自动处理嵌套对象。例如,使用System.Text.Json
:
using System.Text.Json;
class Program
{
static void Main()
{
var address = new Address { Street = "123 Main St", City = "Anytown" };
var person = new Person { Name = "Frank", Age = 32, HomeAddress = address };
string jsonString = JsonSerializer.Serialize(person);
Console.WriteLine(jsonString);
}
}
输出结果类似:{"Name":"Frank","Age":32,"HomeAddress":{"Street":"123 Main St","City":"Anytown"}}
。
对于XML序列化,同样能处理嵌套对象:
using System.Xml.Serialization;
using System.IO;
class Program
{
static void Main()
{
var address = new Address { Street = "456 Elm St", City = "Othercity" };
var person = new Person { Name = "Grace", Age = 29, HomeAddress = address };
XmlSerializer serializer = new XmlSerializer(typeof(Person));
using (StreamWriter writer = new StreamWriter("person.xml"))
{
serializer.Serialize(writer, person);
}
}
}
生成的XML如下:
<?xml version="1.0" encoding="utf-8"?>
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Grace</Name>
<Age>29</Age>
<HomeAddress>
<Street>456 Elm St</Street>
<City>Othercity</City>
</HomeAddress>
</Person>
二进制序列化也能很好地处理嵌套对象,只要嵌套的对象也标记为[Serializable]
:
[Serializable]
public class Address
{
public string Street { get; set; }
public string City { get; set; }
}
[Serializable]
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Address HomeAddress { get; set; }
}
然后进行二进制序列化和反序列化操作与之前简单对象的操作类似。
集合对象
处理集合对象也是常见的需求。例如,我们有一个包含多个Person
对象的列表:
using System.Collections.Generic;
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person() { }
}
class Program
{
static void Main()
{
var people = new List<Person>
{
new Person { Name = "Hank", Age = 35 },
new Person { Name = "Ivy", Age = 27 }
};
// JSON序列化
string jsonString = JsonSerializer.Serialize(people);
Console.WriteLine(jsonString);
// XML序列化
XmlSerializer serializer = new XmlSerializer(typeof(List<Person>));
using (StreamWriter writer = new StreamWriter("people.xml"))
{
serializer.Serialize(writer, people);
}
// 二进制序列化
BinaryFormatter formatter = new BinaryFormatter();
using (FileStream stream = new FileStream("people.bin", FileMode.Create))
{
formatter.Serialize(stream, people);
}
}
}
JSON序列化后的结果是一个包含多个Person
对象的JSON数组。XML序列化会生成一个根元素,包含多个Person
子元素。二进制序列化则将整个列表序列化为二进制数据。
反序列化操作也类似,将相应格式的数据转换回集合对象。例如,JSON反序列化:
string jsonString = "[{\"Name\":\"Hank\",\"Age\":35},{\"Name\":\"Ivy\",\"Age\":27}]";
List<Person> people = JsonSerializer.Deserialize<List<Person>>(jsonString);
if (people != null)
{
foreach (var person in people)
{
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
}
}
性能与优化
JSON序列化性能优化
在System.Text.Json
中,通过使用Utf8JsonWriter
和Utf8JsonReader
可以进一步提升性能,特别是在处理大量数据时。例如,手动使用Utf8JsonWriter
进行序列化:
using System.Text.Json;
using System.IO;
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
class Program
{
static void Main()
{
var person = new Person { Name = "Jack", Age = 30 };
using (MemoryStream stream = new MemoryStream())
{
using (Utf8JsonWriter writer = new Utf8JsonWriter(stream))
{
writer.WriteStartObject();
writer.WriteString("Name", person.Name);
writer.WriteNumber("Age", person.Age);
writer.WriteEndObject();
}
string jsonString = System.Text.Encoding.UTF8.GetString(stream.ToArray());
Console.WriteLine(jsonString);
}
}
}
这种方式避免了反射带来的性能开销,在性能敏感的场景中表现更好。
XML序列化性能优化
对于XML序列化,可以通过预生成序列化器来提升性能。xsd.exe
工具可以根据XML Schema生成C#类和序列化代码。另外,减少不必要的属性和复杂的XML结构定义也能提升序列化和反序列化的速度。
二进制序列化性能优化
二进制序列化本身性能较高,但在处理大型对象图时,可能会遇到性能问题。可以通过优化对象结构,减少不必要的字段,并且合理使用[NonSerialized]
属性来避免序列化不必要的数据,从而提升性能。同时,在反序列化时,注意内存管理,避免大量对象同时加载导致内存溢出。
安全性考虑
JSON序列化安全
在使用JSON反序列化时,要注意防止JSON注入攻击。例如,恶意用户可能构造恶意的JSON数据,导致反序列化过程中执行恶意代码。使用System.Text.Json
时,默认情况下对反序列化有一定的安全性保护。但在使用Newtonsoft.Json
时,需要注意配置JsonSerializerSettings
,例如设置ObjectCreationHandling = ObjectCreationHandling.RejectNull
来防止创建空对象,避免潜在的安全风险。
XML序列化安全
XML序列化存在XXE(XML外部实体注入)风险。要防范这种风险,在反序列化XML时,禁用外部实体解析。例如,在XmlReaderSettings
中设置ProhibitDtd = true
和XmlResolver = null
:
XmlReaderSettings settings = new XmlReaderSettings
{
ProhibitDtd = true,
XmlResolver = null
};
using (XmlReader reader = XmlReader.Create("person.xml", settings))
{
XmlSerializer serializer = new XmlSerializer(typeof(Person));
Person person = (Person)serializer.Deserialize(reader);
}
二进制序列化安全
二进制序列化可能存在反序列化漏洞,例如利用二进制格式的特性进行恶意代码执行。为了安全,要确保只从可信来源进行二进制数据的反序列化,并且对反序列化的类型进行严格验证,避免反序列化不可信的类型。
跨平台与兼容性
JSON跨平台兼容性
JSON是一种跨平台的格式,几乎所有现代编程语言都有JSON处理库。在C#中生成的JSON数据可以很方便地在其他平台如JavaScript、Java、Python等中进行反序列化。同样,其他平台生成的JSON数据也能在C#中顺利反序列化,只要数据结构匹配。
XML跨平台兼容性
XML也是一种广泛支持的跨平台格式。许多平台都有XML处理库,并且XML Schema提供了一种定义数据结构的标准方式,使得不同平台之间可以基于相同的Schema进行数据交互。不过,由于XML的复杂性,在不同平台之间处理XML时,可能需要注意命名空间、编码等细节问题。
二进制序列化跨平台兼容性
二进制序列化在C#中生成的二进制数据通常只能在.NET环境中进行反序列化,因为它依赖于.NET特定的类型信息和格式。如果需要跨平台传输数据,二进制序列化不是一个好的选择,除非目标平台也有相应的.NET兼容环境或专门的二进制格式解析库。
在实际应用中,根据项目的需求,如是否需要与其他平台交互、性能要求、数据可读性等,合理选择序列化与反序列化方案至关重要。每种方案都有其优势和适用场景,通过深入理解和实践,可以充分发挥它们的作用,构建高效、安全且可互操作的应用程序。