为什么需要序列化
1.应用程序的状态可以保存在一个磁盘或者文件中,下次使用时可以恢复;
2.对象可以轻松的复制;
3.对象可以这样进行克隆;
4.网络发送/跨应用程序域 可以进行加密,压缩等;
.NET提供的序列化器
BinaryFormatter(序列化所有的字段,包括私有的)
XmlSerialization(不能序列化私有字段)(测试了一次发现它竟然可以序列化私有字段,还没找到原因,后面需要验证,求指点)
DataContractSerializer(WCF通信时的序列化)
原理
通过反射来查看每个对象的类型中有哪些实例字段,这些字段引用了另外的其他对象,序列化器就知道其他对象也要被序列化,如果有相互引用,序列化器会检测到这一点,只序列化一遍,不会陷入到死循环。如果某个类的对象可以被序列化,而这个类里面引用了另外一个不可序列化的对象,那么在序列化的过程中就会报错。
用序列化实现深复制
//使用序列化来实现深复制(引自《CLR via C#》)
private static object DeepClone(object original)
{
using (MemoryStream stream = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Context = new StreamingContext(StreamingContextStates.Clone);
formatter.Serialize(stream,original);
stream.Position = 0;
return formatter.Deserialize(stream);
}
}
序列化多个对象
可以用一个序列化器同时序列化多个对象,但是反序列化的时候,也要按照序列化的顺序来先进先出,如果类型不一致,就会抛出异常;在序列化之前,序列化器不会检查对象是否可以序列化(是为了提高性能,所以不检查),所以在序列化的过程中可能会序列化失败而抛出异常,序列化失败时,可能一些对象已经序列化进行了,所以流中就包含了一些损坏的数据。有一种方案可以是先序列化在MemoryStream里面,如果所有的都序列化成功了,再将MemoryStream流里面的内容拷贝到你真正希望的目标流中。
使类型可序列化
如果想让某个类的对象可序列化,需要在类名上面加上[Serializable],这个Attribute不能被继承,所以如果你要想让以后子类能够序列化,那么父类一定要加上这些属性。这就是为什么System.Object已经被标记为可序列化了。对于二进制序列化器,序列化时,会序列化所有的字段,不管公有还是私有,Xml序列化器不序列化私有字段。注意:静态字段本身就是类拥有的,它不属于任何对象,我们序列化的是对象的属性,所以它是不能进行序列化;
控制序列化过程
可以通过下面的几个Attribute来控制序列化的过程(前两个Attribute是应用在属性上,后四个Attribute是应用在方法上):
NonSerialized:如果想让某个对象的某个字段不被序列化,而其他的字段都被序列化,那么可以在这个字段上应用这个Attribute。一般包含敏感信息的字段,比如密码等字段都需要加上这个Attribute。
OptionalField:如果某个类序列化后,又新增加了一些字段,那么在反序列化时,因为找不到新增的字段信息,反序列化时会失败。这时,可以在新增的字段上面应用这个Attribute,那么反序列化时就会忽略这些字段,可以正常的反序列化了。
OnSerializing:如果想在序列化前做一些其他的初始化工作,那么可以在相应的方法上面加上这个Attribute。
OnSerialized:如果想在序列化后做一些其他的工作,那么可以在相应的方法上面加上这个Attribute。
OnDeserializing:如果想在反序列化前做一些其他的初始化工作,那么可以在相应的方法上面加上这个Attribute。
OnDeserialized:如果想在反序列化后做一些其他的工作,那么可以在相应的方法上面加上这个Attribute。
序列化到文件
假设serializeObj是需要被序列化的对象,使用BinaryFormatter序列化到文件: XmlSerialization(不能序列化私有字段)
FileStream fs = new FileStream(@"D:\1.dat", FileMode.Create);
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(fs, serializeObj);//如果serializeObj类里面引用了别的对象,而别的对象又没有标记为可序列化,那么就会在这个地方报错
fs.Close();
MessageBox.Show("序列化成功","提示",MessageBoxButtons.OK,MessageBoxIcon.Information);
反序列化:
T serializeObj = new T();
FileStream fs = new FileStream(@"D:\1.dat", FileMode.Open);
BinaryFormatter bf = new BinaryFormatter();
serializeObj = (T)bf.Deserialize(fs);
使用XmlSerialization序列化到文件:
string filePath = @"D:\1.xml";
using (System.IO.StreamWriter writer = new System.IO.StreamWriter(filePath))
{
System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(serializeObj.GetType());
xs.Serialize(writer, serializeObj);
writer.Close();
}
反序列化:
string filePath = @"D:\1.xml";
T serializeObj=new T();
if (System.IO.File.Exists(filePath))
{
using (System.IO.StreamReader reader = new System.IO.StreamReader(filePath))
{
System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(serializeObj.GetType());
object obj = xs.Deserialize(reader);
reader.Close();
serializeObj= obj as T;
}
}
其他
1. 在序列化的类中最好不要用自动属性,因为如果使用了自动属性,那么编译器每次都会为这个自动属性生成一个私有字段,这个私有字段的名字是编译器生成的,并且每次生成的可能都不一样。所以如果使用了自动属性,可能在反序列化的时候就会出错。(但我经过试验,发现没有报错,可以正常的反序列化)
2. 如果想实现更高级的序列化,可以实现ISerializable接口。