C#中的对象序列化
其实在以前的开发过程中,除了做爬虫用到json的时候需要序列化之外,其它地方我很少用到序列化。
像我所处的上位机开发,硬件软件参数会经常发生更改,如果使用序列化,会导致一些兼容性问题。
言归正传,在最近集成一个医院PACS接口时,里面字段太多了,我本来想自己写反射代码生成请求的XML,但后面想想,算了,直接用序列化吧。
这里就对C#中的序列化做个总结,日后再用到也有个参考。
正文
本文介绍了C#中的序列化及4种序列化对象的方法(BinaryFormatter、SoapFormatter、XmlSerializer、JsonSerializer)。
本文只做了入门级的总结 ,如果需深入了解这几种序列化的方法,可以访问MSDN上的文档。
注意:自.NET 5起,已经不推荐使用BinaryFormatter和SoapFormatter了。
对象序列化
序列化是指持久化一个对象的状态到流(如文件流和内存流)的过程。被持久化的数据次序包括以后所有需要用来重建(即反序列化)对象状态所必需的信息。使用序列化,用最小花费来保存各种格式数据就变得轻而易举了。
对象图的作用
当一个对象被持久化到流时,所有的相关数据(基类、包含的对象等)也会被自动序列化,因此,假设我们想持久化一个子类,那么继承链接(即父类、子类的子类)上的所有数据都会被包括进来,也就是对象图。
对象图使用箭头来表示“需要”和“依赖”的关系。
举一个简单的例子,假设有以下的数据模型定义
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