源:来自互联网
“序列化”可被定义为将对象的状态存储到存储媒介中的过程。在此过程中,对象的公共字段和私有字段以
及类的名称(包括包含该类的程序集)都被转换为字节流,然后写入数据流。在以后“反序列化”该对象时,创
建原始对象的精确复本。
一、为什么要选择序列化
一个原因是将对象的状态保持在存储媒体中,以便可以在以后重新创建精确的副本;
另一个原因是通过值将对象从一个应用程序域发送到另一个应用程序域中。
例如,序列化可用于在 ASP.NET 中保存会话状态并将对象复制到 Windows 窗体的剪贴板中。远程处理还可
以使用序列化通过值将对象从一个应用程序域传递到另一个应用程序域中。
二、如何实现对象的序列化及反序列化
要实现对象的序列化,首先要保证该对象可以序列化。而且,序列化只是将对象的属性进行有效的保存,对
于对象的一些方法则无法实现序列化的。
实现一个类可序列化的最简便的方法就是增加Serializable属性标记类。如:
[Serializable()]
public class MEABlock
{
private int m_ID;
public string Caption;
public MEABlock()
{
///构造函数
}
}
即可实现该类的可序列化。
要将该类的实例序列化为到文件中?.NET FrameWork提供了两种方法:
1、XML序列化
使用 XmLSerializer 类,可将下列项序列化。
公共类的公共读/写属性和字段
实现 ICollection 或 IEnumerable 的类。(注意只有集合会被序列化,而公共属性却不会。)
XmlElement 对象。
XmlNode 对象。
DataSet 对象。
要实现上述类的实例的序列化,可参照如下例子:
MEABlock myBlock = new MEABlock();
// Insert code to set properties and fields of the object.
XmlSerializer mySerializer = new XmlSerializer(typeof(MEABlock));
// To write to a file, create a StreamWriter object.
StreamWriter myWriter = new StreamWriter("myFileName.xml");
mySerializer.Serialize(myWriter, MEABlock);
需要注意的是XML序列化只会将public的字段保存,对于私有字段不予于保存。
生成的XML文件格式如下:
<MEABlock>
<Caption>Test</Caption>
</MEABlock>
对于对象的反序列化,则如下:
MEABlock myBlock;
// Constructs an instance of the XmlSerializer with the type
// of object that is being deserialized.
XmlSerializer mySerializer = new XmlSerializer(typeof(MEABlock));
// To read the file, creates a FileStream.
FileStream myFileStream = new FileStream("myFileName.xml", FileMode.Open);
// Calls the Deserialize method and casts to the object type.
myBlock = (MEABlock)mySerializer.Deserialize(myFileStream)
2、二进制序列化
与XML序列化不同的是,二进制序列化可以将类的实例中所有字段(包括私有和公有)都进行序列化操作
。这就更方便、更准确的还原了对象的副本。
要实现上述类的实例的序列化,可参照如下例子:
MEABlock myBlock = new MEABlock();
// Insert code to set properties and fields of the object.
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream("MyFile.bin",FileMode.Create,FileAccess.Write,
FileShare.None);
formatter.Serialize(stream, myBlock);
stream.Close();
对于对象的反序列化,则如下:
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream("MyFile.bin", FileMode.Open,FileAccess.Read,
FileShare.Read);
MEABlock myBlock = (MEABlock) formatter.Deserialize(stream);
stream.Close();
三、如何变相实现自定义可视化控件的序列化、反序列化
对于WinForm中自定义控件,由于继承于System.Windows.Form类,而Form类又是从MarshalByRefObject
继承的,窗体本身无法做到序列化,窗体的实现基于Win32下GUI资源,不能脱离当前上下文存在。
当然可以采用变通的方法实现控件的序列化。这里采用的是记忆类模型。
定义记忆类(其实就是一个可序列化的实体类)用于记录控件的有效属性,需要序列化控件的时候,只
需要将该控件的实例Copy到记忆类,演变成序列化保存该记忆类的操作。
反序列化是一个逆过程。将数据流反序列化成为该记忆类,再根据该记忆类的属性生成控件实例。而对
于控件的一些事件、方法则可以继续使用。
----------------------------------------
什么是序列化?
---.net的运行时环境用来支持用户定义类型的流化的机制。它是将对象实例的状态存储到存储媒体的过程。
在此过程中,先将对象的公共字段和私有字段以及类的名称(包括类所在的程序集)转换为字节流,然后再把字
节流写入数据流。在随后对对象进行反序列化时,将创建出与原
对象完全相同的副本。
序列化的目的:
1、以某种存储形式使自定义对象持久化;
2、将对象从一个地方传递到另一个地方。
实质上序列化机制是将类的值转化为一个一般的(即连续的)字节流,然后就可以将该流写到磁盘文件或任
何其他流化目标上。而要想实际的写出这个流,就要使用那些实现了IFormatter接口的类里的Serialize和
Deserialize方法。
在.net框架里提供了这样两个类:
一、BinaryFormatter
BinaryFormatter使用二进制格式化程序进行序列化。您只需创建一个要使用的流和格式化程序的实例,然后
调用格式化程序的 Serialize 方法。流和要序列化的对象实例作为参数提供给此调用。类中的所有成员变量(甚
至标记为 private 的变量)都将被序列化。
首先我们创建一个类:
[Serializable]
public class MyObject {
public int n1 = 0;
public int n2 = 0;
public String str = null;
}
Serializable属性用来明确表示该类可以被序列化。同样的,我们可以用NonSerializable属性用来明确表示
类不能被序列化。
接着我们创建一个该类的实例,然后序列化,并存到文件里持久:
MyObject obj = new MyObject();
obj.n1 = 1;
obj.n2 = 24;
obj.str = "一些字符串";
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream("MyFile.bin", FileMode.Create,
FileAccess.Write, FileShare.None);
formatter.Serialize(stream, obj);
stream.Close();
而将对象还原到它以前的状态也非常容易。首先,创建格式化程序和流以进行读取,然后让格式化程序对对
象进行反序列化。
IFormatter formatter = new BinaryFormatter();
Stream stream = new 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);
二、SoapFormatter
前面我们用BinaryFormatter以二进制格式来序列化。很容易的我们就能把前面的例子改为用SoapFormatter
的,这样将以xml格式化,因此能有更好的可移植性。所要做的更改只是将以上代码中的格式化程序换成
SoapFormatter,而 Serialize 和 Deserialize 调用不变。对于上面使用的示例,该格式化程序将生成以下结果
。
<SOAP-ENV:Envelope
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">
<SOAP-ENV:Body>
<a1:MyObject id="ref-1">
<n1>1</n1>
<n2>24</n2>
<str id="ref-3">一些字符串</str>
</a1:MyObject>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
在这里需要注意的是,无法继承 Serializable 属性。如果从 MyObject 派生出一个新的类,则这个新的类
也必须使用该属性进行标记,否则将无法序列化。例如,如果试图序列化以下类实例,将会显示一个
SerializationException,说明 MyStuff 类型未标记为可序列化。
public class MyStuff : MyObject
{
public int n3;
}
然而关于格式化器,还有个问题,假设我们只需要xml,但不需要soap特有的额外信息,那么该怎么做?
有两个方案:
1、编写一个实现IFormatter接口的类,采用的方式类似于SoapFormatter,但是可以没有你不需要的信息;
2、使用框架提供的类XmlSerializer。
XmlSerializer类和前两个主流的序列化类的几个不同点是:
1、不需要Serializable属性,Serializable和NonSerializable属性将会被忽略,但是使用XmlIgnore属性,和
NonSerializable属性类似。
2、该类不能安全地访问私有变成员,所以学要将私有成员改为公共成员,或者提供合适的公共特性。
3、要求被序列化的类要有一个默认的构造器。
我们改一下前面的MyObject类为:
public class MyObject {
public int n1;
public String str;
public MyObject(){}
public MyObject(n1,str)
{
this.n1=n1;
this.str=str;
}
public override string ToString()
{
return String.Format("{0}:{1}",this.str,this.n1);
}
}
现在我们用XmlSerializer类来对修改后的MyObject进行序列化。因为XmlSerializer类的构造器里有个Type
参数,所以XmlSerializer对象被明确的 连到该Type参数所表示的类了。XmlSerializer类也有Serialize和
Deserialize方法:
MyObject obj = new MyObject(12,"some string...");
XmlSerializer formatter = new XmlSerializer(typeof(MyObject));
Stream stream = new FileStream("MyFile.xml", FileMode.Create,
FileAccess.Write, FileShare.None);
formatter.Serialize(stream, obj);
//下面是反序列化
stream.Seek(0,SeekOrigin.Begin)
MyObject obj_out=(MyObject)formatter.Deserialize(stream)
stream.Close();
Console.WriteLine(obj_out);
这个简单的列子可以加以扩展,以便利用更多的XmlSerializer功能,包括使用属性控制xml标记、使用xml模
式和进行soap编码。
自定义序列化
如果你希望让用户对类实现序列化,但是对数据流的组织方式不完全满意,那么可以通过在对象上实现
ISerializable 接口来自定义序列化过程。这一功能在反序列化后成员变量的值失效时尤其有用,但是需要为变
量提供值以重建对象的完整状态。除了必须将类申明为 Serializable 的同时,还要要实现 ISerializable接口
,需要实现 GetObjectData 方法以及一个特殊的构造函数,在反序列化对象时要用到此构造函数。
在实现 GetObjectData 方法时,最常调用的SerializationInfo的方法是AddValue,这个方法具有针对所有标准
类型(int、char等等)的重载版本;而 StreamingContext 参数描述给定的序列化流的源和目标,这样我们就可
以知道我们是将对象序列化到持久性存储还是在将他们跨进程或机器序列化。而在反序列化时,我们调用
SerializationInfo提供的一组Getxxx方法,他们针
对所有标准类型数据执行各种AddValue重载版本的逆操作。下代码示例说明了如何在前一部分中提到的 MyObject
类上实现 ISerializable。
[Serializable]
public class MyObject : ISerializable
{
public int n1;
public int 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相同,这个构造函数将会在反序列化的
过程中使用:格式化器从流中反序列化数据,然后通过这个构造函数对对象进行实列化。如果缺少
GetObjectData,编译器将发出警告。
但是,由于无法强制实现构造函数,所以,缺少构造函数时不会发出警告。如果在没有构造函数的情况下尝
试反序列化某个类,将会出现异常。在消除潜在安全性和版本控制问题等方面,当前设计优于 SetObjectData 方
法。例如,如果将 SetObjectData 方法定义为某个接口的一部分,则此方法必须是公共方法,这使得用户不得不
编写代码来防止多次调用 SetObjectData 方法。可以想象,如果某个对象正在执行某些操作,而某个恶意应用程
序却调用此对象的 SetObjectData 方法,将会引起一些潜在的麻烦。
在反序列化过程中,使用出于此目的而提供的构造函数将 SerializationInfo 传递给类。对象反序列化时,对
构造函数的任何可见性约束都将被忽略,因此,可以将类标记为 public、protected、internal 或 private。一
个不错的办法是,在类未封装的情况下,将构造函数标记为 protect。如果类已封装,则应标记为 private。要
还原对象的状态,只需使用序列化时采用的名
称,从 SerializationInfo 中检索变量的值。如果基类实现了 ISerializable,则应调用基类的构造函数,以使
基础对象可以还原其变量。
如果从实现了 ISerializable 的类派生出一个新的类,则只要新的类中含有任何需要序列化的变量,就必须
同时实现构造函数以及 GetObjectData 方法。以下代码片段显示了如何使用上文所示的 MyObject 类来完成此操
作。
[Serializable]
public class ObjectTwo : MyObject
{
public int 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 方法。此时,引用的所有子对象均已完全还原。有些类不使用
上述事件侦听器,很难对它们进行反序列化,散列表便是一个典型的例子。
在反序列化过程中检索关键字/值对非常容易,但是,由于无法保证从散列表派生出的类已反序列化,所以把
这些对象添加回散列表时会出现一些问题。因此,建议目前不要在散列表上调用方法。