什么是序列化和反序列化?
序列化和反序列化是对象和字节流之间的互相转换,将一个对象或者对象图转换成一个字节流的过程就是序列化;反之将一个字节流转换回对象图的过程便是反序列化。
序列化和反序列化的作用(包含但不限于)
- 将对象克隆并作为备份
- 将对象通过网络传输给另一台机器
- 加密和压缩数据
简略的序列化与反序列化示例
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();}
运行后果