目录:
- 序列化、反序列化
- 类型序列化的前提
- 格式化器序列化原理
- 控制序列化和反序列化
一、序列化、反序列化
字节流序列化是将一个对象转换成一个字节流的过程。
字节流反序列化是将一个字节流转回一个对象的过程。
--------序列化----------
对象:p
List<string> p = new List<string>() { "Sun", "Mon", "Star" };
载体:序列化后的字节流载体 ms
System.IO.MemoryStream ms = new System.IO.MemoryStream();
格式化器:序列化工作的工人 formatter
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
序列化:将 p 进行序列化成字节流 ms
formatter.Serialize(ms, p);
-----反序列化------
ms.Position=0; formatter.Deserialize(ms);
实例应用之一:深拷贝
[Serializable] internal class Product:ICloneable { public string Name { get; set; } public int Age { get; set; } public NumberFlag Number { get; set; } public object Clone() { return this.MemberwiseClone(); } public Product DeepClone() { using (System.IO.Stream ms = new System.IO.MemoryStream()) { System.Runtime.Serialization.IFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); formatter.Context = new System.Runtime.Serialization.StreamingContext(System.Runtime.Serialization.StreamingContextStates.Clone); formatter.Serialize(ms, this); ms.Position = 0; return formatter.Deserialize(ms) as Product; } } } [Serializable] internal class NumberFlag { public string Num { get; set; } }
首先我们了解浅拷贝,拷贝的对象中的引用类型的值的改变会互相影响:
Product p1 = new Product() { Name = "1", Age = 1, Number = new NumberFlag() { Num="01"} }; var p2 = p1.Clone() as Product; if (p2 != null) { p2.Number.Num = "22"; Console.WriteLine("p1 Number:{0}.",p1.Number.Num); Console.WriteLine("p2 Number:{0}.",p2.Number.Num); Console.ReadKey(); }
但是,深拷贝能够解决以上问题:
Product p1 = new Product() { Name = "1", Age = 1, Number = new NumberFlag() { Num="01"} }; var p2 = p1.DeepClone(); if (p2 != null) { p2.Number.Num = "22"; Console.WriteLine("p1 Number:{0}.",p1.Number.Num); Console.WriteLine("p2 Number:{0}.",p2.Number.Num); Console.ReadKey(); }
二、类型序列化的前提
类型默认是不可以序列化的,需要加上特性 [Serializable]
SerializableAttribute 这个特性只能应用于:引用类型(class)\值类型(struct)\枚举类型(enum)\委托类型(delegate).
也不能被子类所继承。
[Serializable] public class Phone { } [Serializable] public class iPhone:Phone { }
三、格式化器序列化原理
为了实现格式化器的工作,FCL封装了一个:受保护、不可实例化的类--FormatterServices
System.Runtime.Serialization.FormatterServices
-----格式化器,序列化-------
1、格式化器调用 FormatterServices 的 GetSerializableMembers 方法,这个方法通过反射,返回当前类的成员数组。
public static MemberInfo[] GetSerializableMembers(Type type, StreamingContext context);
2、调用 GetObjectData 方法,返回的是每个成员对应的自己的值。
public static object[] GetObjectData(object obj, MemberInfo[] members);
3、格式化器,将程序集标识和类型名称的全名写入流中。
4、格式化器遍历以上我们的得到的两个数组:MemberInfo[] 和 object[] ,得到成员和成员对应值,并写入流中。
-----格式化器,反序列化--------
1、格式化器读取程序集名称和类型名称,并将程序集标识和类型全名传递给方法 GetTypeFromAssembly,返回我们需要反序列化最终得到的类型。
public static Type GetTypeFromAssembly(Assembly assem, string name);
2、格式化器,调用GetUninitializedObject方法,对当前类型做一些初始化(不调用构造函数,只是为成员分配点内存~)
public static object GetUninitializedObject(Type type);
3、也是调用GetSerializableMembers方法,获取成员 。
4、将流中的数据分配到一个object[]数组中。
5、这样也是获得了成员数组和对应值的数组。将我们的新分配的对象、MemberInfo[]、object[],传入方法,获取我们最终的反序列化的类型。
public static object PopulateObjectMembers(object obj, MemberInfo[] members, object[] data);
四、控制序列化和反序列化
1、个别不需要序列化的字段,标记为:[NonSerialized],此标记只能应用于字段,并能够被子类继承。
[Serializable] public class TotalTime { public TotalTime(int hours) { HoursofDay = hours; Minutes = hours * 60; } public int HoursofDay; [NonSerialized] public int Minutes; }
那好,我们进行字节流序列化,没有问题,正常运行。
TotalTime tt1 = new TotalTime(10); Console.WriteLine("tt1 Minutes:{0}",tt1.Minutes); System.IO.MemoryStream ms = new System.IO.MemoryStream(); System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); formatter.Serialize(ms, tt1); ms.Position=0; var tt2= formatter.Deserialize(ms) as TotalTime; Console.WriteLine("tt2 Minutes:{0}", tt2.Minutes); Console.ReadKey();
因为我们序列化时只有Minutes字段应用了NonSerialized ,所以值就没有序列化到字节流中。
接下来的反序列化,我们也就得不到Minutes在序列化之前的那个值:600.
怎么办?
[Serializable] public class TotalTime { public TotalTime(int hours) { HoursofDay = hours; Minutes = hours * 60; } public int HoursofDay; [NonSerialized] public int Minutes; [System.Runtime.Serialization.OnDeserialized] private void OnDeserializedMinutes(System.Runtime.Serialization.StreamingContext context) { Minutes = HoursofDay * 60; } }
System.Runtime.Serialization.OnDeserialized 是在反序列化之后的操作。
序列化之前
[System.Runtime.Serialization.OnSerializing]
序列化之后
[System.Runtime.Serialization.OnSerialized]
反序列化之前
[System.Runtime.Serialization.OnDeserializing]
反序列化之后
[System.Runtime.Serialization.OnDeserialized]
已上四个属性,自定义的方法必须获取一个流上下文的参数,并返回void,方法的名字我们可以自定义。此方法最好是私有,以防外界调用。
主要还是理解格式化器的流程,对此还可以控制序列化和反序列化的数据,只要我们在通过流获取值数组的那一步进行操作:
public static object[] GetObjectData(object obj, MemberInfo[] members);
--------- 后续会追加Json.Net 的一些对比 ---------
洗刷,洗刷~~~