使用SerializableAttribute进行对象序列化与反序列化
SerializableAttribute属性用于修饰可以序列化的类,如果类支持序列化,便可以使用该属性来修饰。需要注意的并不是所有支持序列化的类都需要被SerializableAttribute来修饰,只有该类需要或可能需要序列化时,如一些实体类,可能需要持久化或用于进程间传输。这样的对象需要被序列化成字节流或其它便于存储或传输的格式,并且通过这个持久化的格式可以还原或反序列化至内存。凡是使用SerializableAttribute进行修饰的类都可以被诸如System.Runtime.Serialization.Formatters.Binary.BinaryFormatter之类的格式化类进行序列化。如:
[Serializable] public class Class1 { public string Name { get; set; } public string ID { get; set; } } Class1 cls1 = new Class1(); cls1.Name = "Class1"; cls1.ID = "cls-1"; BinaryFormatter formatter = new BinaryFormatter(); using (FileStream stream = new FileStream("c:\\Class1.bin", FileMode.Create, FileAccess.Write)) { formatter.Serialize(stream, cls1); } using (FileStream stream = new FileStream("c:\\Class1.bin", FileMode.Open, FileAccess.Read)) { Class1 cls2 = formatter.Deserialize(stream) as Class1; }
但如果Class1没有被SerializableAttribute修饰的话,在formatter.Serialize(stream, cls1)时会抛出SerializationException异常"Type 'XXXX.Class1' in Assembly ..... is not marked as serializable.",因此要想被BinaryFormatter等格式化类序列化,就必须使用SerializableAttribute来修饰。除非你想自己编写序列化方法,通过反射来序列化对象。
另外,更需要注意的是,所有类,结构,枚举等都可以使用SerializableAttribute来进行修饰,但不是所有的类或结构都适合序列化,因为有些字段是无法进行序列化的,或无法再反序列化的。如指针(IntPtr类型),因为在我们序列化时,指针地址会被持久化,然而在反序列化时,该指针地址所指向的内容将不在是原来的内容了。除非该指针不需要被序列化,那么可以使用NonSerializedAttribute来修饰这些不支持序列化的字段。
其实SerializableAttribute仅仅是个标识,用于说明类是可以序列化的。但实际类是否真的合适序列化,需要类的设计者斟酌。设计者需要判断对象的可序列化的字段中是否包含指针等不合适的类型,因为类的使用者只知道类是SerializableAttribute修饰的,便认为是可以序列化的,仅此而已。
.net framework还提供了一个序列化接口System.Runtime.Serialization.ISerializable,该接口主要用于自定义序列化,并且实现该接口的类也要被SerializableAttribute修饰,才能被BinaryFormatter序列化。ISerializable的使用可以跳过NonSerialized的修饰,并运行你控制序列化和反序列化的过程。示例:
[Serializable] public class Class1 : ISerializable { public string Name { get; set; } public string ID { get; set; } [NonSerialized] private DateTime BirthDate; public void SetBirthDate(DateTime birthdate) { this.BirthDate = birthdate; } public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Name", this.Name); info.AddValue("ID", this.ID); info.AddValue("BirthDate", this.BirthDate); } public Class1() { } protected Class1(SerializationInfo info, StreamingContext context) { this.Name = info.GetString("Name"); this.ID = info.GetString("ID"); this.BirthDate = info.GetDateTime("BirthDate"); } }
BinaryFormatter formatter = new BinaryFormatter(); Class1 cls1 = new Class1(); cls1.Name = "Class1"; cls1.ID = "cls-1"; cls1.SetBirthDate(DateTime.Today); using (FileStream stream = new FileStream("c:\\Class1.bin", FileMode.Create, FileAccess.Write)) { formatter.Serialize(stream, cls1); } Class1 cls2 = null; using (FileStream stream = new FileStream("c:\\Class1.bin", FileMode.Open, FileAccess.Read)) { cls2 = formatter.Deserialize(stream) as Class1; }
实现ISerializable接口的类,还要求类必须实现一个具有(SerializationInfo information, StreamingContext context)签名的构造函数,该函数为反序列化过程中调用,如果缺少该构造函数,Class1对象在反序列化时将抛出SerializationException异常"The constructor to deserialize an object of type 'XXX.Class1' was not found."。ISerializable接口的声明:
[ComVisible(true)] public interface ISerializable { [SecurityCritical] void GetObjectData(SerializationInfo info, StreamingContext context); }
ISerializable接口在被Formatter使用时,将会调用GetObjectData方法进行序列化,在反序列化时,将调用类的构造函数(SerializationInfo information, StreamingContext context)。因此如果Class1有子类的话,如Class2,并且Class2也支持序列化,那么Class2也要实现GetObjectData方法,和(SerializationInfo information, StreamingContext context)签名的构造函数。如下Class2的定义:
[Serializable] public class Class2 : Class1 { public int Age { get; set; } public Class2() { } protected Class2(SerializationInfo info, StreamingContext context) : base(info, context) { this.Age = info.GetInt32("Age"); } public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); info.AddValue("Age", this.Age); } }
因此实现ISerializable接口时,最后设置GetObjectData方法为virtual的,要有(SerializationInfo information, StreamingContext context)签名的构造函数,并且该构造函数一般是protected的。