代码改变世界

序列化

2013-04-01 08:39  左眼微笑右眼泪  阅读(420)  评论(0)    收藏  举报

为什么需要序列化

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接口。