C#中的对象序列化

其实在以前的开发过程中,除了做爬虫用到json的时候需要序列化之外,其它地方我很少用到序列化。

像我所处的上位机开发,硬件软件参数会经常发生更改,如果使用序列化,会导致一些兼容性问题。

 

言归正传,在最近集成一个医院PACS接口时,里面字段太多了,我本来想自己写反射代码生成请求的XML,但后面想想,算了,直接用序列化吧。

这里就对C#中的序列化做个总结,日后再用到也有个参考。

 

正文

本文介绍了C#中的序列化及4种序列化对象的方法(BinaryFormatter、SoapFormatter、XmlSerializer、JsonSerializer)。

本文只做了入门级的总结 ,如果需深入了解这几种序列化的方法,可以访问MSDN上的文档。

 

注意:自.NET 5起,已经不推荐使用BinaryFormatter和SoapFormatter了。

https://learn.microsoft.com/zh-cn/dotnet/standard/serialization/binaryformatter-security-guide#binaryformatter-security-vulnerabilities

 

对象序列化

序列化是指持久化一个对象的状态到流(如文件流和内存流)的过程。被持久化的数据次序包括以后所有需要用来重建(即反序列化)对象状态所必需的信息。使用序列化,用最小花费来保存各种格式数据就变得轻而易举了。

 

对象图的作用

当一个对象被持久化到流时,所有的相关数据(基类、包含的对象等)也会被自动序列化,因此,假设我们想持久化一个子类,那么继承链接(即父类、子类的子类)上的所有数据都会被包括进来,也就是对象图。

对象图使用箭头来表示“需要”和“依赖”的关系。

 

举一个简单的例子,假设有以下的数据模型定义

 1     public class Car
 2     {
 3         protected Radio Radio { get; set; }
 4     }
 5 
 6     public class Radio
 7     {
 8 
 9     }
10 
11     public class JamesBondCar : Car
12     {
13 
14     }

 

有一个基类Car,它配有(has a )Radio。JamsBondCar类扩展Car基类。下图显示了一个模拟这些关系可能的对象图

 Car类引用了Radio类(有has-a关系),JamesBondCar类引用了Car类(是is-a关系),也引用了Radio类(因为它继承了这个受保护的成员变量)

 

可以用下面简单的公式来描述

[Car3 , ref 2]    =》 对象3(Car类)依赖于对象2(Radio类)

[Radio 2]  =》 对象2(Radio类)不依赖其它

[JamesBondCar 1, ref 3, ref 2]  =》 对象1(JamesBondCar类)依赖于对象3和对象2。

因此在序列化或反序列化JamesBondCar类的实例时,对象图确认了Radio类型和Car类型也参与了这个过程。

 

定义可序列化的对象

使用[Serializable]特性可以标记为可序列化对象。

使用[NonSerialized]特性可以标记成员不会被序列化

 1    public class Student
 2     {
 3         private int id;
 4 
 5         private string name;
 6 
 7         private string remark;
 8 
 9         public int Id { get => id; set => id = value; }
10         public string Name { get => name; set => name = value; }
11         public string Remark { get => remark; set => remark = value; }
12     }

 

值得注意的是,[Serializable]特性不会被继承,因此子类也需要标记为[Serializable]

对于XmlSerializer和JsonSerializer,可以不用[Serializable]标记也能正常执行。

对于BinaryFormatter或SoapFormatter序列化未使用[Serializable]标记的类时,会抛出SerializationException异常。

 

BinaryFormatter

BinaryFormatter在序列化对象时,会持久化每个类型的完全限定名称和定义程序集的完整名称。也叫类型保真(type fidelity)

这种方式有利也有弊。优点就是,使用BinaryFormatter序列化出来的文件可以移植到其它设备,并完整地恢复到对象的原始状态。缺点就是:因为这些类型都是.NET里面定义的,所以只能供.NET使用,其它编程语言无法使用。

 

使用BinaryFormatter进行序列化的方法如下:

这里以前面的Student类为例

 1 public static void Main()
 2         {
 3             Student student = new Student();
 4             student.Id = 1;
 5             student.Name = "Name";
 6             student.Remark = "Remard";
 7             SaveAsBinaryFile(student, "D:\\student.dat");
 8         }
 9 
10         static void SaveAsBinaryFile(object obj,string filePath)
11         {
12             BinaryFormatter binaryFormatter = new BinaryFormatter();
13             using(FileStream fs = new FileStream(filePath,FileMode.Create))
14             {
15                 binaryFormatter.Serialize(fs, obj);
16             }
17             
18         }

 

 序列化保存出来的文件,使用16进制编辑器打开如下

可见BinaryFormatter.Serialize()是一个负责生成对象图,并将字节顺序移动到流的方法。

这里的流可以是其它类型的流,如内存流,网络流等。示例中使用了文件流,直接保存到了文件。

 

使用BinaryFormatter进行反序列化的方法如下: 

1       static object BinaryFileToObject(string filePath)
2         {
3             BinaryFormatter binaryFormatter = new BinaryFormatter();
4             using(FileStream fs = new FileStream(filePath,FileMode.Open))
5             {
6                 return binaryFormatter.Deserialize(fs);
7             }
8         }
1 var stu = (Student)BinaryFileToObject(file);
2 Console.WriteLine(stu.Id);

 

SoapFormatter

SoapFormatter类型把对象持久化为一个SAOP消息。

SoapFormatter通过使用XML命名空间来持久化原始程序集的跟踪,它不是类型保真的。

 

SoapFormatter进行序列化的方法如下:

说明:需要引用 System.Runtime.Serialization.Formattters.Soap.dll

1 var file = "D:\\soap.soap";
2 Student student = new Student();
3 student.Id = 1;
4 student.Name = "Name";
5 student.Remark = "Remard";
6 SaveAsSOAPFile(student, file);
1  static void SaveAsSOAPFile(object obj, string filePath)
2         {
3             SoapFormatter soapFormatter = new SoapFormatter();
4             using (FileStream fs = new FileStream(filePath, FileMode.Create))
5             {
6                 soapFormatter.Serialize(fs, obj);
7             }
8         }

序列化出来的结果如下:

 

 SoapFormatter进行反序列化的方法如下:

1       static object SOAPFileToObject(string filePath)
2         {
3             SoapFormatter soapFormatter = new SoapFormatter();
4             using (FileStream fs = new FileStream(filePath, FileMode.Open))
5             {
6                 return soapFormatter.Deserialize(fs);
7             }
8         }
1  var stu = (Student)SOAPFileToObject(file);
2  Console.WriteLine(stu.Id);

 

XmlSerializer

使用XmlSerializer可以将对象的所有公有字段/属性持久化为XML元素。

使用XmlSerializer进行序列化的方法如下:

 1  static void Main(string[] args)
 2         {
 3             Student student = new Student();
 4             student.Id = 1;
 5             student.Name = "Name";
 6             student.Remark = "Remark";
 7             SaveAsXmlFile(student, "D:\\output.xml");
 8         }
 9 
10         static void SaveAsXmlFile(object obj,string filePath)
11         {
12             XmlSerializer xmlSerializer = new XmlSerializer(obj.GetType());
13             using(FileStream fs = new FileStream(filePath, FileMode.Create))
14             {
15                 xmlSerializer.Serialize(fs, obj);
16             }
17         }

序列化出来的结果如下:

 

 使用XmlSerializer进行反序列化的方法如下:

1  static T XmlFileToObject<T>(string filePath)
2         {
3             XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
4             using (FileStream fs = new FileStream(filePath, FileMode.Open))
5             {
6                 return (T)xmlSerializer.Deserialize(fs);
7             }
8         }
1 Student stu = XmlFileToObject<Student>("D:\\output.xml");
2 Console.WriteLine(stu.Name);

 

使用XmlSerializer序列化到字符串

 1 public string XmlSerializeToString(object obj)
 2        {
 3             XmlSerializer xmlSerializer = new XmlSerializer(obj.GetType());
 4             StringBuilder stringBuilder = new StringBuilder(); 
//不显示默认命名空间 xsi和xsd 5 var emptyNamespaces = new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty }); 6 var settings = new XmlWriterSettings(); 7 settings.Indent = true; //缩进 8 settings.OmitXmlDeclaration = true; //移除XML声明 9 using (XmlWriter xmlWriter = XmlWriter.Create(stringBuilder,settings)) 10 { 11 xmlSerializer.Serialize(xmlWriter, obj,emptyNamespaces); 12 return stringBuilder.ToString(); 13 } 14 }

 

 

 

在使用XmlSerializer进行序列化时,可以控制 XML数据的生成,这里放到下一遍文章中来进行讲解。

 

JsonSerializer

使用JsonSerializer可以将对象持久化成json字符串。这对于调用WebApi时非常有用。

System.Text.JsonSerializer并不是.Net Framework内置的库,而是伴随.NET Core 3.0一起发布的一套扩展库。

它支持

  • .NET Standard 2.0 and later
  • .NET Framework 4.6.2 and later
  • .NET Core 2.1 and later
  • .NET 5 and later

使用Nuget 搜索安装 System.Text.Json进行安装

 

使用JsonSerializer进行序列化的方法如下:

 1  static void Main(string[] args)
 2         {
 3             Student student = new Student();
 4             student.Id = 1;
 5             student.Name = "Name";
 6             student.Remark = "Remark";
 7 
 8             var json = ObjectConvertToJson(student);
 9             Console.WriteLine(json);
10         }
11 
12         static string ObjectConvertToJson(object obj)
13         {
14             var json = JsonSerializer.Serialize(obj);
15             return json;
16         }

 

运行结果如下:

 

使用JsonSerializer进行反序列化的方法如下:

1  static T JsonToObject<T>(string json)
2         {
3             return JsonSerializer.Deserialize<T>(json);
4         }
1 var stu = JsonToObject<Student>(json);
2 Console.WriteLine(stu.Name);

 

 

参考:

.NET序列化

https://learn.microsoft.com/en-us/dotnet/standard/serialization/

 

XML序列化

https://learn.microsoft.com/en-us/dotnet/standard/serialization/introducing-xml-serialization

 

Json序列化

https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/overview

 

posted @ 2023-05-15 16:12  zhaotianff  阅读(446)  评论(0编辑  收藏  举报