序列化和反序列化
2021/1/29 16:08:32
本文主要是介绍序列化和反序列化,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
什么是序列化和反序列化?
序列化和反序列化是对象和字节流之间的相互转换,将一个对象或者对象图转换成一个字节流的过程就是序列化;反之将一个字节流转换回对象图的过程便是反序列化。
序列化和反序列化的作用(包括但不限于)
- 将对象克隆并作为备份
- 将对象通过网络传输给另一台机器
- 加密和压缩数据
简单的序列化与反序列化示例
private static MemoryStream SerializeToMemory(Object objectGraph) { //构造一个流容纳序列化对象 var stream = new MemoryStream(); //构造序列化格式化器 var formatter = new BinaryFormatter(); //告诉格式化器将对象序列化到一个流中 formatter.Serialize(stream, objectGraph); //返回序列化好的对象流 return stream; } private static Object DeserializeFromMemory(Stream stream) { //构造序列化格式化器 var formatter = new BinaryFormatter(); //告诉格式化器从流中反序列化对象 return formatter.Deserialize(stream); } static void Main() { var objectGraph = new List<string> { "Cat", "Dog", "Fish" }; var stream = SerializeToMemory(objectGraph); //重置 stream.Position = 0; objectGraph = null; //反序列化 objectGraph = (List<string>)DeserializeFromMemory(stream); foreach (var item in objectGraph) { Console.WriteLine("item:{0}", item); } Console.ReadKey(); }
运行结果
格式化器知道如何序列化和反序列化一个对象,想要序列化、反序列化一个对象,只需要调用格式化器的Serialize或Deserialize方法。
**Serialize:**格式化器参考对每个对象的类型进行描述的元数据,从而了解如何序列化完成的对象。序列化时Serialize方法利用反射来查看每个对象的类型中都有哪些实例字段,在这些字段中任何一个引用了其它类型的字段,格式化器就知道也要对这些被引用了的字段进行序列化。
同时格式化器的算法也非常智能,能确保对象图中的每一个对象只序列化一次,如果对象图中的两个对象相互引用,在每个对象之序列化一次的前提下,便能够避免进入无限循环。
**Deserialize:**该方法会检查流的内容,构造流中的所有对象的实例,并初始化所有这些对象中的字段,使它们具有与当初序列化时相同的值。
序列化一个对象时,类型的全名和类型的定义程序集的名称也会被写入流。默认情况下BinaryFormatter会输出程序集的完整标识(无扩展名的文件名、版本号、语言文化和公钥信息)。反序列化一个对象时,格式化器首先获取程序集标识信息,并通过Assembly的Load方法,确保程序集加载到AppDomain中。程序集加载好之后,格式化器在程序集中查找与要反序列化的对象匹配的一个类型。如果程序集不包含要查找的类型,则抛出异常,并终止序列化。反之就创建类型的一个实例,并用流中包含的值对其字段进行初始化。如果类型中的字段与流中读取的字段名不完全匹配,抛出SerializationException异常,并终止序列化。
序列化类型
类型默认是无法序列化的
class Poiont { public int x; public int y; } static void Main() { var poiont = new Poiont() { x = 100, y = 100 }; using(var stream = new MemoryStream()) { new BinaryFormatter().Serialize(stream, poiont); //异常 } }
序列化一个对象图时,格式化器会检查每个对象的类型都是可序列化的。如果对象图中的任意一个对象不是可序列化的,格式化器的Serialize方法都会抛出SerializationException异常
使类型可序列化,让上述代码不再抛出异常
[Serializable] class Poiont { public int x; public int y; }
序列化一个对象图时,可能存在有些对象可以被序列化,有些对象不能序列化的情况。出于性能格式化器不会在序列化前检查对象图中的所有对象都可序列化。所以在序列化对象图过程中抛出SerializationException异常,完全可能已经有有一部分对象已经序列化到了流中。
SerializableAttribute这个定制attribute的两个特征:
- 只能应用于引用类型(class)、值类型(struct)、枚举类型和委托类型,其中枚举类型和委托类型总是可序列化的,不必显示应用SerializableAttribute
- 不会被派生类继承
[Serializable] class super { } //可序列化 class child : super { } //不可序列化
控制序列化和反序列化
SerializableAttribute应用于类型时,所有实例字段(private,protected,public等)都会被序列化。但是类型可能定义了一些不必序列化的实例字段(如序列化后信息无效或者通过简单的计算便能获得),如下代码展示了如何控制序列化和反序列化
[Serializable] class Rectangle { private float m_Width; public float Width { get { return this.m_Width; } } private float m_Height; public float Height { get { return this.m_Height; } } [NonSerialized] private float m_Area; public float Area { get { return this.m_Area; } } public Rectangle(float w, float h) { this.m_Width = w; this.m_Height = h; this.m_Area = w * h; } }
上述代码中Rectangle的对象可以序列化,且格式化器只会序列化对象的m_Width和m_Height字段的值,由于m_Area字段使用了NonSerializedAttribute,所以m_Area字段的值不会被序列化。
static void Main() { using(var stream = new MemoryStream()) { var formatter = new BinaryFormatter(); formatter.Serialize(stream, new Rectangle(10, 5)); stream.Position = 0; var rectangle = (Rectangle)formatter.Deserialize(stream); Console.WriteLine("w = {0}, h = {1}, area = {2}", rectangle.Width, rectangle.Height, rectangle.Area); } }
结果
运行结果证明m_Area字段确实不会被序列化,因为反序列化的时候Area的值被初始化化为0而非50,但这有时候并不是所期望的,以下代码展示了如何修正该问题:
[Serializable] class Rectangle { //... //在原Rectangle类上扩展MyOnDeserialized方法 [OnDeserialized] private void MyOnDeserialized(StreamingContext context) { this.m_Area = this.m_Width * this.m_Height; } }
再次执行以下代码段:
static void Main() { using(var stream = new MemoryStream()) { var formatter = new BinaryFormatter(); formatter.Serialize(stream, new Rectangle(10, 5)); stream.Position = 0; var rectangle = (Rectangle)formatter.Deserialize(stream); Console.WriteLine("w = {0}, h = {1}, area = {2}", rectangle.Width, rectangle.Height, rectangle.Area); } }
结果
修改后的代码仅仅只是添加了一个使用OnDeserializedAttribute标记的MyOnDeserialized方法,每次反序列化类型的一个实例时,格式化器都会检查类型中是否定义了一个应用了OnDeserializedAttribute的方法。如果是就会调用这个方法,调用这个方法的时候所有可序列化的字段都会被正确的赋值。在该方法中,可以访问这些字段来进行一些额外的工作,从而保证对象的完全反序列化。
除了OnDeserializedAttribute,命名空间System.Runtime.Serialization中还定义了OnSerializingAttribute、OnSerializedAttribute、OnDeserializingAttribute。序列化一组对象时,格式化器首先调用对象的标记了OnSerializingAttribute的所有方法。接着序列化对象的所有字段。最后调用对象的标记了OnSerializedAttribute的所有方法。同理在反序列化一组对象时,格式化器先调用对象的标记了OnDeserializingAttribute的所有方法,然后反序列化对象的所有字段,最后调用对象的标记了OnDeserializedAttribute的所有方法。
***注:**在反序列化期间,格式化器发现一个类型中包含应用了OnDeserializedAttribute的方法时,格式化器会将这个对象的引用添加到一个内部列表中。等所有对象反序列化之后,格式化器以相反的方向遍历这个列表(相反顺序调用的原因是内层对象先于外层对象结束反序列化),调用每个对象的OnDeserializedAttribute标记方法,调用这个方法后,所有可序列化的字段都会被正确设置。
FormatterServices如何序列化和反序列化类型实例?
序列化
- 调用FormatterServices的GetSerializableMembers方法,该方法利用反射获取类型的public和private实例字段,方法返回MemberInfo数组,每个元素对应一个可序列化的实例字段
- MemberInfo数组传给FormatterServices的GetObjectData方法,返回Object数组,每个元素都标识了被序列化的那个对象中的一个字段的值。Object数组和MemberInfo数组是并行的(Object数组的0元素便是MemberInfo数组0元素的值)
- 格式化器将程序集标识和类型的完整名称写入流
- 格式化器遍历两个数组中的元素,将每个成员的名称和值写入流
反序列化
- 格式化器从流中读取程序集标识和完整类型名称,如果程序集当前没有加载到AppDomain中,就加载它。如果程序集不能被加载就抛出异常,程序集已加载格式化器将程序集标识信息和类型全名传给FormatterServices的GetTypeFromAssembly方法,方法返回一个Type对象,代表要反序列化的对象类型
- 调用FormatterServices的GetUninitializedObject方法,该方法为一个新对象分配内存,不会调用对象的构造器,对象的所有字段都会被初始化为0或null
- 调用FormatterServices的GetSerializableMembers方法构造并初始化一个MemberInfo数组,方法返回序列化好的现在需要反序列化的一组字段
- 格式化器根据流中包含的数据创建并初始化一个Object数组
- 对新分配的对象、MemberInfo数组以及Object数组(包含字段的值)的引用传给FormatterServices的PopulateObjectMembers方法,这个方法会遍历数组,将每个字段初始化成对应的值,到此反序列化结束
示例代码
static void Main() { var assembly = Assembly.Load("MySerialize"); var type = FormatterServices.GetTypeFromAssembly(assembly, "MySerialize.Rectangle"); var obj = FormatterServices.GetUninitializedObject(type); var mf = FormatterServices.GetSerializableMembers(type); var data = FormatterServices.GetObjectData(new Rectangle(10, 20), mf); var rect = (Rectangle)FormatterServices.PopulateObjectMembers(obj, mf, data); Console.WriteLine("w = {0}, h = {1}", rect.Width, rect.Height); Console.ReadKey(); }
运行结果
ISerializable控制序列化和反序列化
由于格式化器内部会使用反射,而反射的速度是比较慢的,所以会增加序列化和反序列化所花的时间。可以通过让类型实现ISerializable接口避免使用反射也可以序列化和反序列化对象。值得注意的是类型一旦实现了ISerializable接口,它的派生类也必须实现它,并且保证派生类调用了基类的GetObjectData方法。
格式化器在序列化对象图检查每个对象时,如果发现类型实现了ISerializable接口,便会忽略所有定制attribute,改为构造一个新的SerializationInfo对象,这个对象包含了实际要为对象序列化的值的集合。
构造并初始化好SerializationInfo对象后,格式化器调用对象的GetObjectData方法,并传递给方法SerializationInfo对象的引用。GetObjectData方法负责决定需要哪些信息来序列化对象,并将这些信息添加到SerializationInfo对象中,GetObjectData调用SerializationInfo类型提供的AddValue方法指定需要序列化的信息,对每个想要序列化的数据都要调用一次AddValue。
示例代码
[Serializable] class Square : ISerializable, IDeserializationCallback { private int m_Area; public int Area { get { return this.m_Area; } } private int m_Length; public int Length { get { return this.m_Length; } } private string m_Name; public string Name { get { return this.m_Name; } } //只用于反序列化 private SerializationInfo m_SiInfo; public Square(string name, int len) { this.m_Length = len; this.m_Name = name; } //控制反序列化的特殊构造器 [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] protected Square(SerializationInfo info, StreamingContext context) { //反序列化期间为OnDeserialization保存SerializationInfo //在这里不能保证对象已经完全被序列化 this.m_SiInfo = info; } //控制序列化的方法 [SecurityCritical] public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Length", this.m_Length); info.AddValue("Name", this.m_Name); } //所有字段都被反序列化好之后调用该方法 public virtual void OnDeserialization(object sender) { if (this.m_SiInfo == null) return; this.m_Length = this.m_SiInfo.GetInt32("Length"); this.m_Name = this.m_SiInfo.GetString("Name"); this.m_Area = (int)Math.Pow(this.m_Length, 2); } } static void Main() { using (var stream = new MemoryStream()) { var formatter = new BinaryFormatter(); formatter.Serialize(stream, new Square("正方形", 10)); stream.Position = 0; var square = (Square)formatter.Deserialize(stream); Console.WriteLine("边长:{0}的{1}的面积是:{2}", square.Length, square.Name, square.Area); } Console.ReadKey(); }
运行结果
在反序列化的时候,格式化器从流中提取对象时,会为新对象分配新的内存(通过GetUninitializedObject方法),新对象的所有字段都设置为0或null。然后格式化器检查类型是否实现了ISerializable接口,如果是格式化器就尝试调用一个参数和GetObjectData方法一致的特殊构造器。
构造器获取一个SerializationInfo对象的引用,这个对象包含了对象序列化时添加的所有值,反序列化对象的字段时,可以调用和对象序列化时传给AddValue方法的值的类型匹配的一个Get方法,方法的返回值可用于初始化新对象的各个字段。
基类没有实现ISerializable接口,如何定义一个实现它的类型?
如果基类同样实现了ISerializable接口,那么只要调用基类的GetObjectData方法便能完成序列化。一旦基类没有实现GetObjectData接口,这种情况下派生类必须手动序列化基类的值。弊端:如果基类的值是private字段,那么根本就无法实现。
示例代码
[Serializable] class Base { protected string m_Name = "DoubleJ"; public Base() { } } [Serializable] class Child : Base, ISerializable { private DateTime m_DateTime = DateTime.Now; public Child() { } protected Child(SerializationInfo info, StreamingContext context) { //获取类和基类可序列化成员集合 var baseType = this.GetType().BaseType; var mi = FormatterServices.GetSerializableMembers(baseType, context); //反序列化基类字段 for (int i = 0; i < mi.Length; i++) { var fi = (FieldInfo)mi[i]; fi.SetValue(this, info.GetValue(baseType.FullName + "." + fi.Name, fi.FieldType)); } this.m_DateTime = info.GetDateTime("Date"); } public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Date", this.m_DateTime); //获取类和基类可序列化成员集合 var baseType = this.GetType().BaseType; var mi = FormatterServices.GetSerializableMembers(baseType, context); //序列化基类字段到info对象 for (int i = 0; i < mi.Length; i++) info.AddValue(baseType.FullName + "." + mi[i].Name, ((FieldInfo)mi[i]).GetValue(this)); } public override string ToString() { return string.Format("Name = {0}, Date = {1}", this.m_Name, this.m_DateTime); } } static void Main() { using (var stream = new MemoryStream()) { var formatter = new BinaryFormatter(); formatter.Serialize(stream, new Child()); stream.Position = 0; var o = (Child)formatter.Deserialize(stream); Console.WriteLine(o.ToString()); } Console.ReadKey(); }
运行结果
序列化代理
[Serializable] class Version1Type { public Int32 x; } //定义代理类 class MySerializationSurrogate : ISerializationSurrogate { public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) { info.AddValue("x", ((Version1Type)obj).x); } public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) { ((Version1Type)obj).x = info.GetInt32("x"); return obj; } } static void Main() { using (var stream = new MemoryStream()) { var formatter = new BinaryFormatter(); //构造代理选择器对象 var ss = new SurrogateSelector(); //告诉代理选择器为Version1Type对象使用我们的代理 ss.AddSurrogate(typeof(Version1Type), formatter.Context, new MySerializationSurrogate()); //告诉格式化器使用代理选择器 formatter.SurrogateSelector = ss; //序列化 var beforeSerializeData = new Version1Type { x = 6 }; formatter.Serialize(stream, beforeSerializeData); //反序列化 stream.Position = 0; var afterSerializeData = (Version1Type)formatter.Deserialize(stream); Console.WriteLine("beforeSerializeX = {0}", beforeSerializeData.x); Console.WriteLine("afterSerializeX = {0}", afterSerializeData.x); } }
运行结果
使用代理类型后,调用格式化器的Serialize方法时,会在SurrogateSelector维护的哈希表中查找要序列化的每个对象的类型,如果发现匹配类型就调用ISerializationSurrogate对象的GetObjectData方法获取应该写入流的信息。
调用格式化器的Deserialize方法时,会在SurrogateSelector中查找要反序列化的对象类型,如果发现匹配类型就调用ISerializationSurrogate对象的SetObjectData方法来设置反序列化的对象中的字段。
SurrogateSelector对象在内部维护了一个私有哈希表,调用AddSurrogate方法时,Type和StreamingContext构成哈希表的Key,ISerializationSurrogate对象便是哈希表的Value。如果要添加的Key已经存在,则会抛出ArgumentException。
如何将对象反序列化成另一个类型?
利用SerializationBinder类可以很方便地将一个对象反序列化成一个不同的类型,首先要定义好自己的类型如:MySerializationBinder类(名字任意),接着在构造好格式化器后,设置格式化器的Binder属性让它的值等于MySerializationBinder类型实例。在反序列化期间,格式化器发现设置了绑定器,对象在反序列化时格式化器都会调用绑定器的BindToType方法,并向方法传递程序集名称以及格式化器想要反序列化的类型。在内部可实现自己的逻辑返回实际想要反序列化的类型。
示例代码
[Serializable] class Version1Type { public int x; } [Serializable] class Version2Type : ISerializable { public int x; public string name; [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("x", x); info.AddValue("name", name); } [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] private Version2Type(SerializationInfo info, StreamingContext context) { x = info.GetInt32("x"); try { name = info.GetString("name"); } catch (SerializationException) { name = "default value"; } } } class MySerializationBinder : SerializationBinder { public override Type BindToType(string assemblyName, string typeName) { var assemVer1 = Assembly.GetExecutingAssembly().FullName; var typeVer1 = "MySerialize.Version1Type"; if (assemblyName == assemVer1 && typeName == typeVer1) typeName = "MySerialize.Version2Type"; //typeToDeserialize return Type.GetType(String.Format("{0}, {1}", typeName, assemblyName)); } } static void Main() { using (var stream = new MemoryStream()) { var formatter = new BinaryFormatter(); //序列化 Console.WriteLine("序列化的对象类型: " + typeof(Version1Type)); formatter.Serialize(stream, new Version1Type() { x = 10 }); formatter.Binder = new MySerializationBinder(); stream.Position = 0; var type2 = (Version2Type)formatter.Deserialize(stream); Console.WriteLine("反序列化的对象类型: " + type2.GetType()); Console.WriteLine("x = {0}, name = {1}", type2.x, type2.name); } Console.ReadKey(); }
运行结果
这篇关于序列化和反序列化的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-03-01沐雪多租宝商城源码从.NetCore3.1升级到.Net6的步骤
- 2024-05-08首个适配Visual Studio平台的国产智能编程助手CodeGeeX正式上线!C#程序员必备效率神器!
- 2024-03-30C#设计模式之十六迭代器模式(Iterator Pattern)【行为型】
- 2024-03-29c# datetime tryparse
- 2024-02-21list find index c#
- 2024-01-24convert toint32 c#
- 2024-01-24Advanced .Net Debugging 1:你必须知道的调试工具
- 2024-01-24.NET集成IdGenerator生成分布式全局唯一ID
- 2024-01-23用CI/CD工具Vela部署Elasticsearch + C# 如何使用
- 2024-01-23.NET开源的简单、快速、强大的前后端分离后台权限管理系统