介绍
序列化是指将对象例子状态存储到存储媒体
过程
在此过程中
先将对象
公共字段和私有字段以及类
名称(包括类所在
集)转换为字节流
然后再把字节流写入数据流
在随后对对象进行反序列化时
将创建出和原对象完全相同
副本
在面向对象环境中实现序列化机制时
必须在易用性和灵活性的间进行
些权衡
只要您对此过程有足够
控制能力
就可以使该过程在很大程度上自动进行
例如
简单
2进制序列化不能满足需要
或者
由于特定原因需要确定类中那些字段需要序列化
以下各部分将探讨 .NET 框架提供
可靠
序列化机制
并着重介绍使您可以根据需要自定义序列化过程
些重要功能
持久存储
我们经常需要将对象字段值保存到磁盘中
并在以后检索此数据
尽管不使用序列化也能完成这项工作
但这种思路方法通常很繁琐而且容易出错
并且在需要跟踪对象
层次结构时
会变得越来越复杂
可以想象
下编写包含大量对象
大型业务应用
情形
员不得不为每
个对象编写代码
以便将字段和属性保存至磁盘以及从磁盘还原这些字段和属性
序列化提供了轻松实现这个目标
快捷思路方法
公共语言运行时 (CLR) 管理对象在内存中分布
.NET 框架则通过使用反射提供自动
序列化机制
对象序列化后
类
名称、
集以及类例子
所有数据成员均被写入存储媒体中
对象通常用成员变量来存储对其他例子
引用
类序列化后
序列化引擎将跟踪所有已序列化
引用对象
以确保同
对象不被序列化多次
.NET 框架所提供
序列化体系结构可以自动正确处理对象图表和循环引用
对对象图表
唯
要求是
由正在进行序列化
对象所引用
所有对象都必须标记为 Serializable(请参阅基本序列化)
否则
当序列化
试图序列化未标记
对象时将会出现异常
当反序列化已序列化类时
将重新创建该类
并自动还原所有数据成员
值
按值封送
对象仅在创建对象应用
域中有效
除非对象是从 MarshalByRefObject 派生得到或标记为 Serializable
否则
任何将对象作为参数传递或将其作为结果返回
尝试都将失败
如果对象标记为 Serializable
则该对象将被自动序列化
并从
个应用
域传输至另
个应用
域
然后进行反序列化
从而在第 2个应用
域中产生出该对象
个精确副本
此过程通常称为按值封送
如果对象是从 MarshalByRefObject 派生得到则从
个应用
域传递至另
个应用
域
是对象引用
而不是对象本身
也可以将从 MarshalByRefObject 派生得到
对象标记为 Serializable
远程使用此对象时
负责进行序列化并已预先配置为 SurrogateSelector
格式化
将控制序列化过程
并用
个代理替换所有从 MarshalByRefObject 派生得到
对象
如果没有预先配置为 SurrogateSelector
序列化体系结构将遵从下面
标准序列化规则(请参阅序列化过程
步骤)
基本序列化
要使个类可序列化
最简单
思路方法是使用 Serializable 属性对它进行标记
如下所示:
[Serializable]
public MyObject {
public n1 = 0;
public n2 = 0;
public String str = null;
}
以下代码片段介绍说明了如何将此类个例子序列化为
个文件:
MyObject obj = MyObject
;
obj.n1 = 1;
obj.n2 = 24;
obj.str = "些
串";
IFormatter formatter = BinaryFormatter
;
Stream stream = FileStream("MyFile.bin", FileMode.Create,
FileAccess.Write, FileShare.None);
formatter.Serialize(stream, obj);
stream.Close;
本例使用 2进制格式化进行序列化
您只需创建
个要使用
流和格式化
例子
然后
格式化
Serialize 思路方法
流和要序列化
对象例子作为参数提供给此
类中
所有成员变量(甚至标记为 private
变量)都将被序列化
但这
点在本例中未明确体现出来
在这
点上
2进制序列化区别于只序列化公共字段
XML 序列化
将对象还原到它以前状态也非常容易
首先
创建格式化
和流以进行读取
然后让格式化
对对象进行反序列化
以下代码片段介绍说明了如何进行此操作
IFormatter formatter = BinaryFormatter
;
Stream stream = FileStream("MyFile.bin", FileMode.Open,
FileAccess.Read, FileShare.Read);
MyObject obj = (MyObject) formatter.Deserialize(fromStream);
stream.Close;
// 下面是证明
Console.WriteLine("n1: {0}", obj.n1);
Console.WriteLine("n2: {0}", obj.n2);
Console.WriteLine("str: {0}", obj.str);
上面所使用 BinaryFormatter 效率很高
能生成非常紧凑
字节流
所有使用此格式化
序列化
对象也可使用它进行反序列化
对于序列化将在 .NET 平台上进行反序列化
对象
此格式化
无疑是
个理想工具
需要注意
是
对对象进行反序列化时并不
构造
对反序列化添加这项约束
是出于性能方面
考虑
但是
这违反了对象编写者通常采用
些运行时约定
因此
开发人员在将对象标记为可序列化时
应确保考虑了这
特殊约定
如果要求具有可移植性请使用 SoapFormatter
所要做
更改只是将以上代码中
格式化
换成 SoapFormatter
而 Serialize 和 Deserialize
不变
对于上面使用
举例
该格式化
将生成以下结果
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:SOAP- ENC=http://schemas.xmlsoap.org/soap/encoding/
xmlns:SOAP- ENV=http://schemas.xmlsoap.org/soap/envelope/
SOAP-ENV:encodingStyle=
"http://schemas.microsoft.com/soap/encoding/clr/1.0
http://schemas.xmlsoap.org/soap/encoding/"
xmlns:a1="http://schemas.microsoft.com/clr/assem/ToFile">
些
串
需要注意是
无法继承 Serializable 属性
如果从 MyObject 派生出
个新
类
则这个新
类也必须使用该属性进行标记
否则将无法序列化
例如
如果试图序列化以下类例子
将会显示
个 SerializationException
介绍说明 MyStuff 类型未标记为可序列化
public MyStuff : MyObject
{
public n3;
}
使用序列化属性非常方便但是它存在上述
些限制
有关何时标记类以进行序列化(
类编译后就无法再序列化)
请参考有关介绍说明(请参阅下面
序列化规则)
选择性序列化
类通常包含不应被序列化字段
例如
假设某个类用
个成员变量来存储线程 ID
当此类被反序列化时
序列化此类时所存储
ID 对应
线程可能不再运行
所以对这个值进行序列化没有意义
可以通过使用 NonSerialized 属性标记成员变量来防止它们被序列化
如下所示:
[Serializable]
public MyObject
{
public n1;
[NonSerialized] public n2;
public String str;
}
自定义序列化
可以通过在对象上实现 ISerializable 接口来自定义序列化过程这
功能在反序列化后成员变量
值失效时尤其有用
但是需要为变量提供值以重建对象
完整状态
要实现 ISerializable
需要实现 GetObjectData 思路方法以及
个特殊
构造
在反序列化对象时要用到此构造
以下代码举例介绍说明了如何在前
部分中提到
MyObject 类上实现 ISerializable
[Serializable]
public MyObject : ISerializable
{
public n1;
public n2;
public String str;
public MyObject
{
}
protected MyObject(SerializationInfo info, StreamingContext context)
{
n1 = info.GetInt32("i");
n2 = info.GetInt32("j");
str = info.GetString("k");
}
public virtual void GetObjectData(SerializationInfo info,
StreamingContext context)
{
info.AddValue("i", n1);
info.AddValue("j", n2);
info.AddValue("k", str);
}
}
在序列化过程中 GetObjectData 时
需要填充思路方法
中提供
SerializationInfo 对象
只需按名称/值对
形式添加将要序列化
变量
其名称可以是任何文本
只要已序列化
数据足以在反序列化过程中还原对象
便可以自由选择添加至 SerializationInfo
成员变量
如果基对象实现了 ISerializable
则派生类应
其基对象
GetObjectData 思路方法
需要强调是
将 ISerializable 添加至某个类时
需要同时实现 GetObjectData 以及特殊
构造
如果缺少 GetObjectData
编译器将发出警告
但是
由于无法强制实现构造
所以
缺少构造
时不会发出警告
如果在没有构造
情况下尝试反序列化某个类
将会出现异常
在消除潜在安全性和版本控制问题等方面
当前设计优于 SetObjectData 思路方法
例如
如果将 SetObjectData 思路方法定义为某个接口
部分
则此思路方法必须是公共思路方法
这使得用户不得不编写代码来防止多次
SetObjectData 思路方法
可以想象
如果某个对象正在执行某些操作
而某个恶意应用
却
此对象
SetObjectData 思路方法
将会引起
些潜在
麻烦
在反序列化过程中使用出于此目
而提供
构造
将 SerializationInfo 传递给类
对象反序列化时
对构造
任何可见性约束都将被忽略
因此
可以将类标记为 public、protected、
ernal 或 private
个不错
办法是
在类未封装
情况下
将构造
标记为 protect
如果类已封装
则应标记为 private
要还原对象
状态
只需使用序列化时采用
名称
从 SerializationInfo 中检索变量
值
如果基类实现了 ISerializable
则应
基类
构造
以使基础对象可以还原其变量
如果从实现了 ISerializable 类派生出
个新
类
则只要新
类中含有任何需要序列化
变量
就必须同时实现构造
以及 GetObjectData 思路方法
以下代码片段显示了如何使用上文所示
MyObject 类来完成此操作
[Serializable]
public ObjectTwo : MyObject
{
public num;
public ObjectTwo : base
{
}
protected ObjectTwo(SerializationInfo si, StreamingContext context) :
base(si,context)
{
num = si.GetInt32("num");
}
public override void GetObjectData(SerializationInfo si,
StreamingContext context)
{
base.GetObjectData(si,context);
si.AddValue("num", num);
}
}
切记要在反序列化构造中
基类
否则
将永远不会
基类上
构造
并且在反序列化后也无法构建完整
对象
对象被彻底重新构建但是在反系列化过程中
思路方法可能会带来不良
副作用,
被
思路方法可能引用了在
时尚未反序列化
对象引用
如果正在进行反序列化
类实现了 IDeserializationCallback
则反序列化整个对象图表后
将自动
OnSerialization 思路方法
此时
引用
所有子对象均已完全还原
有些类不使用上述事件侦听器
很难对它们进行反序列化
散列表便是
个典型
例子
在反序列化过程中检索关键字/值对非常容易
但是
由于无法保证从散列表派生出
类已反序列化
所以把这些对象添加回散列表时会出现
些问题
因此
建议目前不要在散列表上
思路方法
序列化过程步骤
在格式化上
Serialize 思路方法时
对象序列化按照以下规则进行:
检查格式化是否有代理选取器
如果有
检查代理选取器是否处理指定类型
对象
如果选取器处理此对象类型
将在代理选取器上
ISerializable.GetObjectData
如果没有代理选取器或有却不处理此类型将检查是否使用 Serializable 属性对对象进行标记
如果未标记
将会引发 SerializationException
如果对象已被正确标记将检查对象是否实现了 ISerializable
如果已实现
将在对象上
GetObjectData
如果对象未实现 Serializable将使用默认
序列化策略
对所有未标记为 NonSerialized
字段都进行序列化
版本控制
.NET 框架支持版本控制和并排执行并且
如果类
接口保持
致
所有类均可跨版本工作
由于序列化涉及
是成员变量而非接口
所以
在向要跨版本序列化
类中添加成员变量
或从中删除变量时
应谨慎行事
特别是对于未实现 ISerializable
类更应如此
若当前版本
状态发生了任何变化(例如添加成员变量、更改变量类型或更改变量名称)
都意味着如果同
类型
现有对象是使用早期版本进行序列化
则无法成功对它们进行反序列化
如果对象状态需要在区别版本间发生改变
类
作者可以有两种选择:
实现 ISerializable这使您可以精确地控制序列化和反序列化过程
在反序列化过程中正确地添加和解释未来状态
使用 NonSerialized 属性标记不重要成员变量
仅当预计类在区别版本间
变化较小时
才可使用这个选项
例如
把
个新变量添加至类
较高版本后
可以将该变量标记为 NonSerialized
以确保该类和早期版本保持兼容
序列化规则
由于类编译后便无法序列化所以在设计新类时应考虑序列化
需要考虑
问题有:是否必须跨应用
域来发送此类?是否要远程使用此类?用户将如何使用此类?也许他们会从我
类中派生出
个需要序列化
新类
只要有这种可能性
就应将类标记为可序列化
除下列情况以外
最好将所有类都标记为可序列化:
所有类都永远也不会跨越应用
域
如果某个类不要求序列化但需要跨越应用
域
请从 MarshalByRefObject 派生此类
类存储仅适用于其当前例子特殊指针
例如
如果某个类包含非受控
内存或文件句柄
请确保将这些字段标记为 NonSerialized 或根本不序列化此类
某些数据成员包含敏感信息在迍
最新评论