彻底吃透.NET中序列化反序列化

文章目录

在 .NET 领域,序列化(Serialization)简单来说就是把对象"拍扁"成一段数据流(如二进制、XML、JSON),以便存到硬盘或通过网络发给别人。反序列化(Deserialization)则是把这段流"吹气"还原成内存里的 C# 对象。

序列化是把一个内存中的对象的信息转化成一个可以持久化保存的形式,以便于保存或传输,序列化的主要作用是不同平台之间进行通信,常用的有序列化有json、xml、文件等,下面就逐个讲下这三种序列化的方法。

在 C# 开发中,你主要会接触到以下三种类型的序列化方式:

注意:原生的 BinaryFormatter 因为存在严重的安全漏洞,微软已经在 .NET 5+ 以后将其标注为过时(Deprecated),千万不要在生产环境使用它。

对象的序列化主要有两种用途

1.把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中

我们经常需要将对象的字段值保存到磁盘中,并在以后检索此数据。尽管不使用序列化也能完成这项工作,但这种方法通常很繁琐而且容易出错,并且在需要跟踪对象的层次结构时,会变得越来越复杂。

可以想象一下编写包含大量对象的大型业务应用程序的情形,程序员不得不为每一个对象编写代码,以便将字段和属性保存至磁盘以及从磁 盘还原这些字段和属性。序列化提供了轻松实现这个目标的快捷方法。公共语言运行时 (CLR) 管理对象在内存中的分布,.NET 框架则通过使用反射提供自动的序列化机制。对象序列化后,类的名称、程序集以及类实例的所有数据成员均被写入存储媒体中。对象通常用成员变量来存储对其他实例的引用。类序列化后,序列化引擎将跟踪所有已序列化的引用对象,以确保同一对象不被序列化多次。

.NET 框架所提供的序列化体系结构可以自动正确处理对象图表和循环引用。对对象图表的唯一要求是,由正在进行序列化的对象所引用的所有对象都必须标记为 Serializable(请参阅基 本序列化)。否则,当序列化程序试图序列化未标记的对象时将会出现异常。当反序列化已序列化的类时,将重新创建该类,并自动还原所有数据成员的值。

2.在网络上传送对象的字节序列

对象仅在创建对象的应用程序域中有效。除非对象是从MarshalByRefObject派生得到或标记为 Serializable,否则,任何将对象作为参数传递或将其作为结果返回的尝试都将失败。

如果对象标记为 Serializable,则该对象将被自动序列化,并从一个应用程序域传输至另一个应用程序域,然后进行反序列化,从而在第二个应用程序域中产生出该对象的一个精确副本。此过程通常称为按值封送。如果对象是从MarshalByRefObject派生得到,则从一个应用程序域传递至另一个应用程序域的是对象引用,而不是对象本身。也可以将从MarshalByRefObject派生得到的对象标记为Serializable。

3.序列化和反序列化的主要作用有

  1. 在进程下次启动时读取上次保存的对象的信息
  2. 在不同的AppDomain或进程之间传递数据
  3. 在分布式应用系统中传递数据

什么叫序列化?

我们都知道对象是暂时保存在内存中的,不能用U盘考走了,有时为了使用介质转移对象,并且把对象的状态保持下来,就需要把对象保存下来,这个过程就叫做序列化,通俗点,就是把人的魂(对象)收伏成一个魂器(可传输的介质

什么叫反序列化?

就是再把介质中的东西还原成对象,把魂器还原成人的过程。

在进行这些操作的时候都需要这个可以被序列化,要能被序列化,就得给类头加[Serializable]特性。通常网络程序为了传输安全才这么做。


序列化及反序列化

C#中用于对象和json相互转换的原生类有两个:

DataContractJsonSerializer

JavaScriptSerializer,

其中JavaScriptSerializer主要用于web的浏览器和服务器之间的通信。

这里主要讲DataContractJsonSerializer的使用,要使用DataContractJsonSerializer,先要在项目中引用System.Runtime.Serialization。首先准备一个测试的类Book:

二进制序列化

序列化为二进制

[Serializable]

1、Serializable特性的作用

序列化的attribute,是为了利用序列化的技术 准备用于序列化的对象必须设置 [System.Serializable] 标签,该标签指示一个类可以序列化。 便于在网络中传输和保存这个标签是类可以被序列化的特性,表示这个类可以被序列化。

csharp 复制代码
[Serializable]
public class Student
{
   public string Name { get; set; }
   public int Sex { get; set; }
   public int Age { get; set; }
   public Address Address { get; set; }
}
[Serializable]
public class Address
{
   public string City { get; set; }
   public string Road { get; set; }
}

上述代码中我们在类的头部加入了 Serializable 特性,这代表着整个类对象都需要序列化,如果我们不需要序列化其中某个属性的话只需在该属性上加上 NonSerialized 特性即可。下面我们来看一下序列化和反序列化的代码:

csharp 复制代码
class Program
{
    static void Main(string[] args)
    {
        #region 序列化
        Student student = new Student
        {
            Name = "Tom",
            Age = 20,
            Sex = 1,
            Address = new Address
            {
                City = "NYC",
                Road = "ABC"
            }
        };
        //二进制序列化
        BinaryFormatter binFormat = new BinaryFormatter();
        //返回保存的文档
        string fileName = Path.Combine(@"D:\", @"321.txt");
        using (Stream fStream = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite))
        {
            binFormat.Serialize(fStream, student);
        }
        #endregion
        #region 反序列化
        using (Stream fStream = new FileStream(fileName, FileMode.Open, FileAccess.ReadWrite))
        {
            fStream.Position = 0;
            student = (Student)binFormat.Deserialize(fStream);
        }
        Console.WriteLine("Name: " + student.Name);
        Console.WriteLine("Sex: " + student.Sex);
        Console.WriteLine("Age: " + student.Age);
        Console.WriteLine("Address: " + student.Address.City + " " + student.Address.Road);
        #endregion
        Console.ReadLine();
    }
}

第二种写法

csharp 复制代码
using System;
using System.Collections;
using System.IO;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters.Binary;
using System.Diagnostics;

namespace ConsoleApp1
{
	[Serializable]      // 表示该类可以被序列化
	class Person
	{
		private string name;
		[NonSerialized]  // 表示下面这个age字段不进行序列化
		private int age;
		public string Name
		{
			get { return name; }
			set { name = value; }
		}
		public int Age
		{
			get { return age; }
			set { age = value; }
		}
		public Person() { }
		public Person(string name, int age)
		{
			this.name = name;
			this.age = age;
		}

		public void SayHi()
		{
			Console.WriteLine("我是{0}, 今年{1}岁", name, age);
		}
	}
		class Program
		{
			static void Main(string[] args)
			{
			string filePath = Path.Combine(@"D:\", @"321.txt");

			// Use this for initialization
				List<Person> listPers = new List<Person>();
				Person per1 = new Person("张三", 18);
				Person per2 = new Person("李四", 20);
				listPers.Add(per1);
				listPers.Add(per2);
				SerializeMethod(listPers, filePath);  // 序列化
				DeserializeMethod();  // 反序列化
				Console.WriteLine("Done ! ");
				Console.ReadKey();

			void DeserializeMethod()     // 二进制反序列化
			{
				FileStream fs = new FileStream(filePath, FileMode.Open);
				BinaryFormatter bf = new BinaryFormatter();
				List<Person> list = bf.Deserialize(fs) as List<Person>;

				if (list != null)
				{
					for (int i = 0; i < list.Count; i++)
					{
						list[i].SayHi();
					}
				}
				fs.Close();
			}
		}
        private static void SerializeMethod(List<Person> listPers, string filePath)
        {
            FileStream fs = new FileStream(filePath, FileMode.Create);
            BinaryFormatter bf = new BinaryFormatter();
            bf.Serialize(fs, listPers);
            fs.Close();
        }
    }
}

写法三

csharp 复制代码
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
namespace SerializableTest
{
    class Program
    {
        static void Main(string[] args)
        {
            //创建一个格式化程序的实例
            IFormatter formatter = new BinaryFormatter();
            Console.WriteLine("对象序列化开始......");
            var me = new Person
                         {
                             Sno = "200719",
                             Name = "yuananyun",
                             Sex="man",
                             Age=22
                         };
            //创建一个文件流
            Stream stream = new FileStream("c:/personInfo.txt", FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
            formatter.Serialize(stream, me);
            stream.Close();
            Console.WriteLine("序列化结束!\n");
            Console.WriteLine("反序列化开始......");
            //反序列化
            Stream destream = new FileStream("c:/personInfo.txt", FileMode.Open,
            FileAccess.Read, FileShare.Read);
            var stillme = (Person)formatter.Deserialize(destream);
            stream.Close();
            Console.WriteLine("反序列化结束,输出对象信息......");
            Console.WriteLine(stillme.DisplayInfo());
            Console.ReadKey();
        }
    }
}
csharp 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace SerializableTest
{
    [Serializable]
    public class Person
    {
        public string Sno { get; set; }
        public string Name { get; set; }
        public string Sex { get; set; }
        public int Age { get; set; }
        public string DisplayInfo()
        {
            return "我的学号是:" +Sno+ "\n我的名字是:"+Name + "\n我的性别为:"+Sex+"\n我的年龄:"+Age+"\n";
        }
    }
}

JSON

JSON 的英文全称是 JavaScript Object Notation ,是一种轻量级的数据交换格式。完全独立于语言的文本格式 易于人阅读和编写同时也易于机器解析和生成。JSON 是目前互联网中主流的数据交换格式,同时也是很多开发语言配置文件的主流格式。

  1. 在 .NET 中存在两个类对 JSON 进行处理,分别是 DataContractJsonSerializer 和 JavaScriptSerializer ,这两个类的功能基本一致。 DataContractJsonSerializer 位于命名空间 System.Runtime.Serialization.Json 下,它的特点是必须使用 DataContract 以及 DataMember 属性标记成员。 JavaScriptSerializer 位于命名空间 System.Web.Script.Serialization 下,通过名字和它所在的命名空间我们可以得知它主要用在网络通信中,它可以序列化任何类型的对象。同样 .NET 中也存在一个强大的第三方 JSON 序列化/反序列化库 Newtonsoft.Json ,他比前两个类用起来要方便很多。下面我们对这三个序列化/反序列化的方式分别进行讲解。

DataContractJsonSerializer

  • 首先我们需要在项目中引用 DataContractJsonSerializer 所在的命名空间,这里要注意的时我们不仅要在项目中添加引用 System.Runtime.Serialization 还需要添加引用 System.ServiceModel.Web 。将这两个命名空添加到命名空间后就可以在代码中引入 DataContractJsonSerializer 的命名空间了。

Json支持下面两种数据结构:

  • 键值对的集合--各种不同的编程语言,都支持这种数据结构;
  • 有序的列表类型值的集合--这其中包含数组,集合,矢量,或者序列,等等。

Json有下面几种表现形式

1.对象

一个没有顺序的"键/值",一个对象以花括号"{"开始,并以花括号"}"结束,在每一个"键"的后面,有一个冒号,并且使用逗号来分隔多个键值对。例如:

var user = {"name":"Manas","gender":"Male","birthday":"1987-8-8"}

2.数组

设置值的顺序,一个数组以中括号"["开始,并以中括号"]"结束,并且所有的值使用逗号分隔,例如:

var userlist = [{"user":{"name":"Manas","gender":"Male","birthday":"1987-8-8"}},{"user":{"name":"Mohapatra","Male":"Female","birthday":"1987-7-7"}}]

3.字符串

任意数量的Unicode字符,使用引号做标记,并使用反斜杠来分隔。例如:

  • var userlist = "{"ID":1,"Name":"Manas","Address":"India"}"
csharp 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.Serialization;
namespace JsonSerializerAndDeSerializer
{
    [DataContract]
   public class Student
    {
        [DataMember]
       public int ID { get; set; }
        [DataMember]
       public string Name { get; set; }
        [DataMember]
       public int Age { get; set; }
        [DataMember]
       public string Sex { get; set; }
    }
}

数据契约(DataContract) 服务契约定义了远程访问对象和可供调用的方法,数据契约则是服务端和客户端之间要传送的自定义数据类型。一旦声明一个类型为DataContract,那么该类型就可以被序列化在服务端和客户端之间传送

只有声明为DataContract的类型的对象可以被传送,且只有成员属性会被传递,成员方法不会被传递。默认情况下类中的所有成员属性都不会被序列化传输出去,如果需要将成员数据传输出去就需要在属性头部加入 DataMember

1.DataContractJsonSerializer

DataContractJsonSerializer类帮助我们序列化和反序列化Json,他在程序集 System.Runtime.Serialization.dll下的System.Runtime.Serialization.Json命名空间里。

csharp 复制代码
  Student stu = new Student()
             {
                 ID = 1,
                 Name = "曹操",
                 Sex = "男",
                 Age = 1000
             };
            //序列化
            DataContractJsonSerializer js = new DataContractJsonSerializer(typeof(Student));
            MemoryStream msObj = new MemoryStream();
            //将序列化之后的Json格式数据写入流中
            js.WriteObject(msObj, stu);
            msObj.Position = 0;
            //从0这个位置开始读取流中的数据
            StreamReader sr = new StreamReader(msObj, Encoding.UTF8);
            string json = sr.ReadToEnd();
            sr.Close();
            msObj.Close();
            Console.WriteLine(json);

            //反序列化
            string toDes = json;
            //string to = "{\"ID\":\"1\",\"Name\":\"曹操\",\"Sex\":\"男\",\"Age\":\"1230\"}";
            using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(toDes)))
            {
                DataContractJsonSerializer deseralizer = new DataContractJsonSerializer(typeof(Student));
                Student model = (Student)deseralizer.ReadObject(ms);// //反序列化ReadObject
                Console.WriteLine("ID=" + model.ID);
                Console.WriteLine("Name=" + model.Name);
                Console.WriteLine("Age=" + model.Age);
                Console.WriteLine("Sex=" + model.Sex);
            }
            Console.ReadKey();
  Student stu = new Student()
             {
                 ID = 1,
                 Name = "曹操",
                 Sex = "男",
                 Age = 1000
             };
            //序列化
            DataContractJsonSerializer js = new DataContractJsonSerializer(typeof(Student));
            MemoryStream msObj = new MemoryStream();
            //将序列化之后的Json格式数据写入流中
            js.WriteObject(msObj, stu);
            msObj.Position = 0;
            //从0这个位置开始读取流中的数据
            StreamReader sr = new StreamReader(msObj, Encoding.UTF8);
            string json = sr.ReadToEnd();
            sr.Close();
            msObj.Close();
            Console.WriteLine(json);

            //反序列化
            string toDes = json;
            //string to = "{\"ID\":\"1\",\"Name\":\"曹操\",\"Sex\":\"男\",\"Age\":\"1230\"}";
            using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(toDes)))
            {
                DataContractJsonSerializer deseralizer = new DataContractJsonSerializer(typeof(Student));
                Student model = (Student)deseralizer.ReadObject(ms);// //反序列化ReadObject
                Console.WriteLine("ID=" + model.ID);
                Console.WriteLine("Name=" + model.Name);
                Console.WriteLine("Age=" + model.Age);
                Console.WriteLine("Sex=" + model.Sex);
            }
            Console.ReadKey();
  Student stu = new Student()
             {
                 ID = 1,
                 Name = "曹操",
                 Sex = "男",
                 Age = 1000
             };
            //序列化
            DataContractJsonSerializer js = new DataContractJsonSerializer(typeof(Student));
            MemoryStream msObj = new MemoryStream();
            //将序列化之后的Json格式数据写入流中
            js.WriteObject(msObj, stu);
            msObj.Position = 0;
            //从0这个位置开始读取流中的数据
            StreamReader sr = new StreamReader(msObj, Encoding.UTF8);
            string json = sr.ReadToEnd();
            sr.Close();
            msObj.Close();
            Console.WriteLine(json);

            //反序列化
            string toDes = json;
            //string to = "{\"ID\":\"1\",\"Name\":\"曹操\",\"Sex\":\"男\",\"Age\":\"1230\"}";
            using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(toDes)))
            {
                DataContractJsonSerializer deseralizer = new DataContractJsonSerializer(typeof(Student));
                Student model = (Student)deseralizer.ReadObject(ms);// //反序列化ReadObject
                Console.WriteLine("ID=" + model.ID);
                Console.WriteLine("Name=" + model.Name);
                Console.WriteLine("Age=" + model.Age);
                Console.WriteLine("Sex=" + model.Sex);
            }
            Console.ReadKey();

2.JavaScriptJsonSerializer

csharp 复制代码
Student stu = new Student()
{
ID = 1,
Name = "关羽",
Age = 2000,
Sex = "男"
};

JavaScriptSerializer js = new JavaScriptSerializer();
string jsonData = js.Serialize(stu);//序列化
Console.WriteLine(jsonData);

////反序列化方式一:
string desJson = jsonData;
//Student model = js.Deserialize<Student>(desJson);// //反序列化
//string message = string.Format("ID={0},Name={1},Age={2},Sex={3}", model.ID, model.Name, model.Age, model.Sex);
//Console.WriteLine(message);
//Console.ReadKey();

////反序列化方式2
dynamic modelDy = js.Deserialize<dynamic>(desJson); //反序列化
string messageDy = string.Format("动态的反序列化,ID={0},Name={1},Age={2},Sex={3}",
modelDy["ID"], modelDy["Name"], modelDy["Age"], modelDy["Sex"]);//这里要使用索引取值,不能使用对象.属性
Console.WriteLine(messageDy);
Console.ReadKey();

3.JSON.NET(性能最好)

csharp 复制代码
List<Student> lstStuModel = new List<Student>()
{

new Student(){ID=1,Name="张飞",Age=250,Sex="男"},
new Student(){ID=2,Name="潘金莲",Age=300,Sex="女"}
};

//Json.NET序列化
string jsonData = JsonConvert.SerializeObject(lstStuModel);

Console.WriteLine(jsonData);
Console.ReadKey();

//Json.NET反序列化
string json = @"{ 'Name':'C#','Age':'3000','ID':'1','Sex':'女'}";
Student descJsonStu = JsonConvert.DeserializeObject<Student>(json);//反序列化
Console.WriteLine(string.Format("反序列化: ID={0},Name={1},Sex={2},Sex={3}", descJsonStu.ID, descJsonStu.Name, descJsonStu.Age, descJsonStu.Sex));
Console.ReadKey();

XML

UTF-8

互联网的普及,强烈要求出现一种统一的编码方式。UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式。其他实现方式还包括 UTF-16(字符用两个字节或四个字节表示)和 UTF-32(字符用四个字节表示),不过在互联网上基本不用。重复一遍,这里的关系是,UTF-8 是 Unicode 的实现方式之一。

UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。

UTF-8 的编码规则很简单,只有二条:

1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。

2)对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。

下表总结了编码规则,字母x表示可用编码的位。

XML和JSON比较

SON 和 XML 都用于接收 web 服务端的数据。

JSON 和 XML在写法上有所不同,如下所示:

JSON 实例

xml 复制代码
{
    "sites": [
    { "name":"菜鸟教程" , "url":"www.runoob.com" },
    { "name":"google" , "url":"www.google.com" },
    { "name":"微博" , "url":"www.weibo.com" }
    ]
}

XML 实例

xml 复制代码
<sites>
  <site>
    <name>菜鸟教程</name> <url>www.runoob.com</url>
  </site>
  <site>
    <name>google</name> <url>www.google.com</url>
  </site>
  <site>
    <name>微博</name> <url>www.weibo.com</url>
  </site>
</sites>

JSON 与 XML 的相同之处:

  • JSON 和 XML 数据都是 "自我描述" ,都易于理解。
  • JSON 和 XML 数据都是有层次的结构
  • JSON 和 XML 数据可以被大多数编程语言使用

JSON 与 XML 的不同之处:

  • JSON 不需要结束标签
  • JSON 更加简短
  • JSON 读写速度更快
  • JSON 可以使用数组

最大的不同是:XML 需要使用 XML 解析器来解析,JSON 可以使用标准的 JavaScript 函数来解析。
JSON.parse(): 将一个 JSON 字符串转换为 JavaScript 对象。
JSON.stringify(): 于将 JavaScript 值转换为 JSON 字符串。


为什么 JSON 比 XML 更好?

XML 比 JSON 更难解析。

JSON 可以直接使用现有的 JavaScript 对象解析。

针对 AJAX 应用,JSON 比 XML 数据加载更快,而且更简单:

使用 XML

  • 获取 XML 文档
  • 使用 XML DOM 迭代循环文档
  • 接数据解析出来复制给变量

使用 JSON

  • 获取 JSON 字符串
  • JSON.Parse 解析 JSON 字符串

避坑指南

特性(Attribute)控制

通过给类成员打标签,可以精确控制序列化行为:

  • [JsonIgnore]:不参与序列化,常用于保护敏感数据或防止循环引用。
  • [JsonPropertyName]:当 C# 命名规范(大驼峰)和 JSON 规范(小驼峰/下划线)冲突时做映射。

性能陷阱

  • 反射开销:传统的序列化器在运行时利用反射获取类型信息,这很耗 CPU。
  • Source Generation (源生成器) :.NET 6+ 引入了 JsonSourceGenerationOptions。它在编译时就生成好序列化代码,不再依赖反射,极大提升了 AOT(原生编译)环境和高性能场景的启动速度。

循环引用问题

如果 A 引用 B,B 又引用 A,序列化器会陷入死循环报错。

  • 解法 :在 JsonSerializerOptions 中设置 ReferenceHandler.Preserve
相关推荐
one9962 小时前
C# 的进程间通信(IPC,Inter-Process Communication)
开发语言·c#
CreasyChan2 小时前
unity-向量数学:由浅入深详解
unity·c#
spencer_tseng2 小时前
org.eclipse.wst.common.project.facet.core.xml could not be read.
xml·java·eclipse
切糕师学AI2 小时前
.NET 中常见的内存泄漏场景及解决方案
.net·内存泄漏
liangshanbo12152 小时前
JSON-RPC 2.0 协议详解
网络协议·rpc·json
专注VB编程开发20年12 小时前
C#全面超越JAVA,主要还是跨平台用的人少
java·c#·.net·跨平台
小鸡吃米…15 小时前
Python - XML 处理
xml·开发语言·python·开源
小猪快跑爱摄影16 小时前
【AutoCad 2025】【C#】零基础教程(四)——MText 常见属性
c#·autocad
炼钢厂17 小时前
C#6——DateTime
c#