运行时序列化
一、序列化与反序列化
序列化是将对象图转换成字节流的过程,反序列化是将字节流转换回对象图的过程
class Program { static void Main(string[] args) { List<string> objectGraph = new List<string>() {"关羽", "吕蒙"}; Stream stream = SerialzeToMemory(objectGraph); stream.Position = 0;//反序列化前,定位到内存流的起始位置 objectGraph = (List<string>)DeserialzeFromMemory(stream); foreach (var obj in objectGraph) { Console.WriteLine(obj); } Console.ReadKey(); } private static MemoryStream SerialzeToMemory(object objectGraph) { //构造流来容纳序列化的对象 MemoryStream stream = new MemoryStream(); //构造序列化格式化器来执行所有真正的工作 BinaryFormatter formatter = new BinaryFormatter(); //告诉格式化器将对象序列化到流中 formatter.Serialize(stream, objectGraph); return stream; } private static object DeserialzeFromMemory(Stream stream) { //构造序列化格式化器来做所有真正的工作 BinaryFormatter formatter = new BinaryFormatter(); //告诉格式化器从流中反序列化对象 return formatter.Deserialize(stream); } }
注意事项:
①保证代码为序列化和反序列化使用相同的格式化器
②可将多个对象图序列化到一个流中
private static List<Customer> s_customers = new List<Customer>(); private static List<Order> s_pendingOrders = new List<Order>(); private static List<Order> s_processedOrders = new List<Order>(); static void Main(string[] args) { //将多个对象图序列化一个流中 MemoryStream stream = new MemoryStream(); BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(stream, s_customers); formatter.Serialize(stream, s_pendingOrders); formatter.Serialize(stream, s_processedOrders); //反序列化应用程序的完整状态(和序列化时的顺序一样) s_customers = (List<Customer>) formatter.Deserialize(stream); s_pendingOrders = (List<Order>) formatter.Deserialize(stream); s_processedOrders = (List<Order>) formatter.Deserialize(stream); Console.ReadKey(); }
③序列化对象时,类型的全名和类型定义程序集的全名会被写入流。BinaryFormatter默认输出程序集的完整标识,其中包含程序集的文件名(无扩展名)、版本号、语言文化以及公钥信息。反序列化对象时,格式化器首先获取程序集标识信息,并通过调用System.Reflection.Assembly的Load方法确保程序集已加载到正在执行的AppDoamin中。程序集加载好之后,格式化器在程序集中查找与要反序列化的对象匹配类型。找不到匹配类型则抛出异常,不再对更多的对象进行序列化,找到匹配的类型,就创建类型的实例,并用流中包含的值对其字段进行初始化。如果类型中的字段与流中读取的字段名不完全匹配,就抛出SerializetionException异常,不再对更多的对象进行序列化
二、使类型可序列化
1,必须在需要序列化的类型上加上System.SerializebleAttibute特性
2,SerializebleAttibute这个定制特性只能应用与引用类型(class)、值类型(struct)、枚举类型(enum)和委托类型(delegate)。注意类型和委托总是可序列化的,所以不必显示应用SerializebleAttibute特性。
3,SerializebleAttibute特性不会被派生类继承
5,System.Object应用了SerializebleAttibute特性
6,序列化会读取对象的所有字段,不管这些字段声明为public、protected、internal还是private
三、控制序列化和反序列化
[Serializable] public sealed class Circle { public double m_radius=10;//半径 [NonSerialized]//当前字段不会被序列化(反序列化时,此值为0) public double m_area=10;//面积 [OnDeserializing] private void OnDeserializing(StreamingContext context) { m_area = 20;//举例:在这个类型新版本中 } [OnDeserialized] private void OnDeserialized(StreamingContext context) { m_area = 10;//举例:根据字段值初始化瞬时状态 } [OnSerializing] private void OnSerializing(StreamingContext context) { m_area = 10;//举例:在序列化前,修改任何需要修改的状态 } [OnSerialized] private void OnSerialized(StreamingContext context) { m_area = 10;//举例:在序列化后,恢复任何需要恢复的状态 } }
①定义的方法必须获取一个StreamingContext参数,并返回void。方法名称可随意命名
②调用顺序:序列化一组对象时先调用标记了OnSerializing的方法,然后调用标记了OnSerialized特性的方法。反序列化时先调用标记了OnDeserializing的方法,然后调用标记了OnDeserialized的方法
如果序列化类型的实例,在类型中添加新字段,然后视图反序列化不包含新字段的对象,格式化器会抛出SerializationExcption异常。可在新增字段中加上OptionalFieldAttribute特性
四、格式化器如何序列化类型实例
FCL在System.Runtime.Serialization命名空间提供了一个FormatterServices类型,该类型只包含静态方法,而且该类型不能实例化
1,格式化器如何自动序列化类型引用了SerializableAttribute特性的对象
①格式化器调用FormatterServices的GetSerializableMembers方法。
public static MemberInfo[] GetSerializableMembers(Type type, StreamingContext context);
这个方法利用反射获取类型的public和private实例字段(标记了NonSerializedAttribute特性的字段除外)。方法返回由MemberInfo对象构成的数组,其中每个元素都对应一个可序列化的实例字段
②对象被序列化,System.Reflection.MemberInfo对象数组传给FormatterServices的静态方法GetObjectData
public static object[] GetObjectData(object obj, MemberInfo[] members);
这个方法返回一个Object数组,其中每个元素都标识了被序列化的那个对象的一个字段的值。object数组中的元素0是MemberInfo数组中的元素0所标识的那个成员的值
③格式化器将程序集标识和类型的完整名称写入流中
④格式化器然后遍历两个数组中的元素,将每个成员的名称和值写入流中
2,格式化器如何自动反序列化类型应用了SerializableAttribute特性的对象
①格式化器流中读取程序集标识和完整类名称。如果程序集当没有加载到AppDomain中,就加载它。如果程序集不能加载,就抛出一个SerializetionException异常,对象不能反序列化。如果程序集已加载,格式化器将程序集标识信息和类型全名传给FormatterServices的静态方法GetTypeFromAssembly
public static Type GetTypeFromAssembly(Assembly assem, string name);
这个方法返回一个System.Type对象,它代表要反序列化的那个对象的类型
②格式化器调用FormatterServices的静态方法GetUninitializedObject
public static object GetUninitializedObject(System.Type type)
这个方法为一个新对象分配内存,但不为对象调用构造器。然而,对象的所有字节都被初始化成null或0
③格式化器现在构造并初始化一个MemberInfo数组,具体做法和前面一样,都是调用FormatterServices的GetSerializableMembers方法。这个方法返回序列化好、现在需要反序列化的一组字段
④格式化器根据流中包含的数据创建并初始化一个Object数组
⑤将新分配对象、MemberInfo数组以及并行Object数组(其中包含字段值)的引用传给FormatterServices的静态方法PopulateObjectMembers
public static object PopulateObjectMembers(object obj, System.Reflection.MemberInfo[] members, object[] data)
这个方法遍历数组,将每个字段初始化成对应的值。
五、控制序列化/反序列化的数据
1,ISerializable
格式化器内部使用的是反射,反射的速度比较慢。为了对序列化和反序列化完全控制,需要实现System.Runtime.Serialization.ISerializable接口。派生此接口的类型最好是派生类。
public interface ISerializable { void GetObjectData(SerializationInfo info,StreamingContext context){} }
建议向GetObjectData方法和特殊构造器应用以下特性
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
2,SerializationInfo(包含了要为对象序列化的值得集合)
构造此对象需要传递两个参数Type和System.Runtime.Serialization.IFormatterConverter
SerializationInfo对象的AddValue方法添加要序列化的信息
class Program { static void Main(string[] args) { CustomerSerializeable c = new CustomerSerializeable() {Name = "张三", Age =12}; MemoryStream ms = new MemoryStream(); BinaryFormatter b = new BinaryFormatter(); b.Serialize(ms, c); ms.Position = 0; var aa=(CustomerSerializeable)b.Deserialize(ms); Console.WriteLine(aa.Name); Console.WriteLine(aa.Age);//不会被序列化 Console.ReadKey(); } } //设置此类型最好是密封的 [Serializable] public sealed class CustomerSerializeable : ISerializable,IDeserializationCallback { public string Name { get; set; } public int Age { get; set; } //只用于反序列化 private SerializationInfo m_siInfo; public CustomerSerializeable(){} //用于控制反序列化的特殊构造器(不加此构造函数反序列化将抛异常) [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] private CustomerSerializeable(SerializationInfo info, StreamingContext sc) { m_siInfo = info; } //用于控制序列化的方法 [SecurityCritical] public void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Name", Name, typeof (string)); } //所有key/value对象都反序列化好之后调用的方法 public void OnDeserialization(object sender) { if(m_siInfo==null)return;//从不设置,直接返回 Name = m_siInfo.GetString("Name"); } }
如果一个字段的类型实现了ISerializable接口,就不要在字段上调用GetObjectData。相反,调用AddValue来添加字段
3,IFormatterConverter
FormatterConverter类型调用System.Convert类的各种静态方法在不同的核心类型之间对值进行转换
4,要实现 ISerializable但基类型没有实现怎么办?
[Serializable] public class Base { protected string m_name = "张三"; } [Serializable] public class Derived : Base,ISerializable { private DateTime m_date=DateTime.Now; public Derived(){} //用于控制反序列化的特殊构造器(不加此构造函数反序列化将抛异常) [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] private Derived(SerializationInfo info, StreamingContext context) { m_date = info.GetDateTime("Date"); Type baseType = GetType().BaseType; MemberInfo[] mis = FormatterServices.GetSerializableMembers(baseType, context);//获取类型所有可序列化的成员 //从info对象反序列化基类的字段 foreach (var memberInfo in mis) { FieldInfo f = (FieldInfo) memberInfo; f.SetValue(this, info.GetValue(baseType.FullName + "+" + f.Name, f.FieldType)); } } [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] public void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Date", m_date); Type baseType = GetType().BaseType; MemberInfo[] mis = FormatterServices.GetSerializableMembers(baseType, context);//获取类型所有可序列化的成员 //将基类字段序列化到info对象中 foreach (var memberInfo in mis) { FieldInfo f = (FieldInfo)memberInfo; info.AddValue(baseType.FullName + "+" + f.Name,f.GetValue(this)); } } public override string ToString() { return string.Format("Name={0},Date={1}", m_name, m_date); } }
class Program { static void Main(string[] args) { Derived c = new Derived(); MemoryStream ms = new MemoryStream(); BinaryFormatter b = new BinaryFormatter(); b.Serialize(ms, c); ms.Position = 0; var aa=(Derived)b.Deserialize(ms); Console.WriteLine(aa); Console.ReadKey(); } }
六、序列化和反序列化单实例
class Program { static void Main(string[] args) { Singleton[] ss = new Singleton[] {Singleton.GetSingleton(), Singleton.GetSingleton()}; Console.WriteLine(ss[0]==ss[1]);//True using (MemoryStream ms = new MemoryStream()) { BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(ms, ss); ms.Position = 0; var ss2 = (Singleton[]) formatter.Deserialize(ms); Console.WriteLine(ss2[0] == ss2[1]);//True Console.WriteLine(ss[0] == ss2[0]);//True } Console.ReadKey(); } } [Serializable] public sealed class Singleton : ISerializable { private static Singleton s_theOneObject = new Singleton(); public string Name = "张三"; public DateTime Date=DateTime.Now; private Singleton(){} public static Singleton GetSingleton(){return s_theOneObject;} [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] public void GetObjectData(SerializationInfo info, StreamingContext context) { info.SetType(typeof(SingletonSerializationHepler)); } [Serializable] private sealed class SingletonSerializationHepler : IObjectReference { //这个方法在对象(他没有字段)反序列化之后调用 public object GetRealObject(StreamingContext context) { return GetSingleton(); } } }
七、序列化代理
class Program { static void Main(string[] args) { using (var ms = new MemoryStream()) { IFormatter formatter = new BinaryFormatter(); //构造一个SurrogateSelector(代理选择器)对象 SurrogateSelector ss = new SurrogateSelector(); //告诉代理选择器为Datetime对象使用我们的代理 ss.AddSurrogate(typeof (DateTime), formatter.Context, new LocalTimeSerializationSurrogate()); //注意:AddSurrogate可多次调用来登记多个代理 //告诉格式化选择器使用代理对象 formatter.SurrogateSelector = ss; //创建一个DateTime来代表机器上的本地时间,并序列化它 DateTime localDateTime = DateTime.Now; formatter.Serialize(ms, localDateTime); //反序列化 ms.Position = 0; var dt = (DateTime) formatter.Deserialize(ms); Console.WriteLine(dt); } Console.ReadKey(); } } public sealed class LocalTimeSerializationSurrogate : ISerializationSurrogate { public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) { //将DateTime从本地时间转换成UTC info.AddValue("Date", ((DateTime) obj).ToShortDateString()); } public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) { //将DateTime从UTC转换成本地时间 return Convert.ToDateTime(info.GetString("Date")); } }