l 为什么要序列化?
为了达到软件“人性化”的目的,很多开发制作软件的程序商们非常习惯将某些预订好的设置(诸如“皮肤”等)设定成最佳状态,保存到一个信息文件中;或者当用户改变这一状态时软件自身也会将用户设置的状态记载下来,这样用户下次开启软件就不必费神费力地重新去设置适合自己的软件布局状态了。
像这种“设置记载”的做法最早(当然,现在仍然保留着)是使用类似于文本文件的形式(ini文件)进行存储,大概格式如下:
[区域块名称]
;注释
变量(字段)名=值
当一个软件开启的时候,它首先加载该文件(如果该文件存在,没有人为改动出现异常的情况下),其步骤分别是:1)读取相关设置的区域块名称(标识符,表明统一的某个模块的设置)2)读取其名下所有的变量(字段,又称属性)和值 3)根据标识符和变量要求,改变软件界面,呈现给用户看。
比如说一个股票软件的主界面(呈现用户自选股)的页面,就应该包含诸如“背景色”、“涨的颜色”、“跌的颜色”等属性。那么按照中国大陆的习惯,可能设置文件中应该存在这样的信息:
[SelfChoose]
;选股票的SQL
Sql=”select * from Customers where cid=?”
;背景色
BackGroundColor=black
;涨的颜色
RisingUp=red
;跌的颜色
DropDown=green
这样做当然是一目了然的,但是由于是文本文件,所以客户可以轻易打开直接进行编辑修改。如果有些小孩或者是恶意的黑客们加入了些具备破坏性的代码,其结果可想而知。而且读取这样的文件虽然可以借助微软的DLL类库(WritePrivateProfileString和GetPrivateProfileString),但是读取的时候仍旧是离散的,每次读入一个值,还要根据“区域块名”和某个隶属的“变量名”决定究竟是设置改变哪个块的哪个部分,如果一个软件有很多设置的话代码量是非常可怕的。
后来经过改进,微软推出了注册表和XML来替代它,这两种方式都有自己的对应类和接口等操作函数(方法),不过XML的安全性还是不高(仍旧基于文本格式,可以打开直接修改),至于注册表一旦写坏,用户就有“冒着重装系统的危险”。
现在的问题在于:如何让客户的设置按照预期的目标进行存储,并且要符合OOP的思想,而且又要做到安全性高(最好是用记事本无法打开,或者打开根本看不懂)?
要做到这点,我们不得不考虑这样的设计:
1)基于OOP思想的设置:它本身必须是一个类,因为只有这样才可能符合OOP的逻辑(不一定有方法和事件,但私有变量和公开属性必不可少);最好做到这个文件读入不是“分批、离散”的“字符串式”,而是通过某种途径可以还原成一个类进行操作。
2)安全性:要么加密,要么以二进制方式直接存储对象。
幸运的是,微软已经帮助我们实现了这种模式;这就是下面要讨论的“OOP序列化”问题。
l 如何序列化?
当需要读取的时候,在界面的Load的事件中可以这样写:
当用户关闭窗体的时候,在关闭的事件中保存用户的设置:
这里需要注意几个问题:
1)要“(反)序列化”的时候,必须引入“using System.Runtime.Serialization.Formatters.Binary;”
命名空间。
2)BinaryStream是一个二进制文件的读写类,其中包含着两个方法:
I)void Serialize (Stream s, object obj):要被写入(序列化)的类以及写入的流(写入的位置)。
II)object Deserialize (Stream s):要被读出类(反序列化)的源(注意还原成原来的类的时候需要强制类型转换)。
l (反)序列化的另类妙用
大家不知道是否注意这样一个问题——为什么BinaryStream的输入、输出参数竟然是Stream(而不是FileStream)?难道说(反)序列化不仅仅是用来做设置记录一类的?
我们说Stream是文件读写流的一个抽象类,许多IO类(FileStream,MemoryStream等)都继承了该类。现在就来使用MomoryStream类构造一个简单的“深复制”方法
所谓“深复制”,就是指将类自身中的所有数据——无论什么类型,完全克隆拷贝一份返回给用户(某些类实现了“Clone”方法,但是都是“潜复制”,即把自身直接返回出去——return this)这样做的问题显然会带来一个问题——那就是“牵一发动全身”(任何地方改变了这个副本类的某个属性,则原来版本也“同步”变更了)。而且,如果克隆的类中带有引用类型(诸如自定义类或数组),则问题更加复杂化。传统的做法往往是一一拷贝这些引用类型的副本,但是这样做显然费时费力。
现在既然(反)序列化可以把类存储在文件中,并且还原成原始保存的状态,那么为何不使用这种手段进行“深复制”呢?不过,要提醒诸位的是,(反)序列化实现深复制不必每次把类存储在特定文件中,只要在内存中进行就可以了,所以要使用到MemoryStream类。典型代码如下:
以上XXX类实现了ICloneable接口方法,使用了内存式的“(反)序列化克隆”手段达到了目的。此处注意两点:
1)不能使用using代码块,因为using代码块会隐含调用Dispose()方法,而MemoryStream没有这个方法(没有实现IDisposable接口)。
2)序列化之后,必须设置Position=0,否则发生异常(因为MemoryStream在序列化之后指针已经在末尾了,此时反序列化需要从头读取的;FileStream不存在这样的问题)。