[Serializable]在C#中的作用-NET 中的序列化和烦序列化介绍

介绍
序列化是指将对象例子状态存储到存储媒体过程在此过程中先将对象公共字段和私有字段以及类名称(包括类所在集)转换为字节流然后再把字节流写入数据流在随后对对象进行反序列化时将创建出和原对象完全相同副本
在面向对象环境中实现序列化机制时必须在易用性和灵活性的间进行些权衡只要您对此过程有足够控制能力就可以使该过程在很大程度上自动进行例如简单 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:xsi=http://www.w3.org/2001/XMLSchema-instance
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">


1
24




需要注意无法继承 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 或根本不序列化此类
某些数据成员包含敏感信息在迍

Tags:  什么是序列化 对象序列化 java序列化 序列化 序列化的作用

延伸阅读

最新评论

发表评论