static void Main(string[] args) { var objectGraph = new List<string> { "Jeff", "Kristin", "Aidan", "Grant" }; Stream stream = SerializerToMemory(objectGraph); //为了演示,将一切都重置 stream.Position = 0; objectGraph = null; objectGraph = (List<string>)DeserializerFromMemory(stream); foreach (var s in objectGraph) { Console.WriteLine(s); }}
private static MemoryStream SerializerToMemory(Object objectGraph)
{
//构造一个流来容纳序列化的对象
MemoryStream stream = new MemoryStream();
//构造一个序列化格式器,它负责所有的辛苦工作
BinaryFormatter formatter = new BinaryFormatter();
//告诉格式化器将对象序列化到流中
formatter.Serialize(stream, objectGraph);
return stream;
}
private static Object DeserializerFromMemory(Stream stream)
{
//构造一个序列化格式器来做所有辛苦工作
BinaryFormatter formatter = new BinaryFormatter();
return formatter.Deserialize(stream);
}
看起来一切都简单!SerializerToMemory方法构造一个MemoryStream对象。这个对象标明要将序列化好的对象放到哪里。然后,方法构造一个BinaryFormatter对象。格式化器是实现了System.Runtime.Serialization.IFormatter接口的一个类型,它知道如何序列化和反序列化一个对象图。FCL提供了两个格式化器:BinaryFormatter和System.Runtime.Serialization.Formatters.Soap.SoapFormatter(System.Runtime.Serialization.Formatters.Soap.dll程序集中实现的)。
注意 从.NET Framework3.5起,SoapFormatter类已被废弃,应避免在生产过程中使用。然而,对序列化代码进行调试时,它仍然有一定的用途,因为它能生成便于阅读的XML文本。要在生产过程中产用XML序列化和反序列化,请参见XmlSerializer 和DataContractSerializer类
要序列化一个对象图,只需要调用格式化器的Serialize方法,并向它传递两样东西:对一个流对象的引用,以及要序列化对象图的一个引用。流对象标识了序列化好的字节应放到哪里,它可以使从System.IO.Stream抽象类派生的任何一个对象。也就是说,可将对象图序列化成一个MemoryStream、FileStream或者NetworkStream等等。
Serialize的第二个参数是一个对象引用。这个对象可以使任何东西,可以使一个Int32,String,DateTime,Exception,List<String>或者Dictionary<Int32,DateTime>等等。
objectGraph参数引用的对象可饮用其他对象。例如,objectGraph可引用一个集合,而这个集合引用一个数组。这些对象还可以继承引用其他对象。当格式化器的Serialize方法被调用时,对象图中的所有对象都被序列化到流中。
格式化器参数对每个对象的类型进行描述的元数据,从而了解如何序列化完整的对象图。序列化时,Serialize方法利用反射来查看每个对象的类型中都有哪些实例字段。在这些字段中,任何一个引用了其他对象,格式化器的Serialize方法就知道哪些对象也要序列化。
格式化器的算法非常智能。它们知道如何确保对象图中的每个对象都只序列化一次。换言之,如果对象图的两个对象相互引用,格式化器就会检测到这一点,每个对象都只序列化一次,避免进入无限循环。
在上诉代码的SerializerToMemory方法中,当格式化器的Serialize方法返回后,MemoryStream直接返回给调用者。应用程序可以按照自己希望的任何方式利用这个字节数组的内容。例如,可以把他保存到一个文件中,复制到剪切板中或者通过网络发送等。
DeserializerFromMemory方法将一个流反序列化成一个对象图。该方法比用于序列化对象图的方法更简单。在代码中,我构造了一个BinaryFormatter,然后调用它的Deserialize方法。这个方法获取流作为参数,返回对反序列化好的对象图中的根对象的一个引用。
注意:下面是一个有趣而使用的方法,它利用序列化创建对象的一个拷贝(或者说一个克隆体):
private static Object DeepClone(Object original) { using (MemoryStream stream = new MemoryStream()) { BinaryFormatter formatter = new BinaryFormatter(); formatter.Context = new System.Runtime.Serialization.StreamingContext(System.Runtime.Serialization.StreamingContextStates.Clone); //将对象图序列化到内存流中 formatter.Serialize(stream,original); //反序列化前,定为到内存流的起始位置 stream.Position = 0; //将对象图反序列化成一组新对象,并且向调用者返回对象图的根(深拷贝) return formatter.Deserialize(stream); } }
在内部,格式化器的Deserialize方法会检查流的内容,构造流中所有对象的实例,并初始化这些对象的所有字段,使他们具有和当初序列化时相同的值,通常将Deserialize方法返回的对象引用转型为应用程序期待的类型。
在这个时候,我觉得有必要补充几点注意事项。首先,是由你来保证代码为序列化和反序列化使用相同的格式化器。例如,不要写代码用SoapFormatter序列化一个对象,再用BinaryFormatter反序列化。如果Deserialize发现自己解释不了一个流的内容,就会抛出一个System.Runtime.Serialization.SerializationException异常。
其次,可将多个对象图序列化到一个流中,这是很有用的一个操作。例如,假如有以下两个类定义:
[Serializable] sealed class Customer { } [Serializable] sealed class Order { }
然后,在应用程序的主要类中,定义了以下静态字段:
private static List<Customer> s_customers = new List<Customer>(); private static List<Order> s_pendingOrders = new List<Order>(); private static List<Order> s_processedOrders = new List<Order>();
现在,可以利用如下所示的一个方法,将应用程序的状态序列化到单个流中:
private static void SaveApplicationState(Stream stream) { BinaryFormatter binary = new BinaryFormatter(); //序列化我们的应用程序的完整状态 binary.Serialize(stream, s_customers); binary.Serialize(stream, s_pendingOrders); binary.Serialize(stream, s_processedOrders); }
为了重新构建应用程序状态,可以使用如下所示的一个方法发序列化状态:
private static void RestoreApplicationState(Stream stream) { BinaryFormatter binary = new BinaryFormatter(); //反序列化我们应用程序的完整状态(和序列化的顺序一样) s_customers = (List<Customer>)binary.Deserialize(stream); s_pendingOrders = (List<Order>)binary.Deserialize(stream); s_processedOrders = (List<Order>)binary.Deserialize(stream); }
第三也是最后一点注意事项与程序集有关。序列化一个对象时,类型的全名和类型的定义程序集的名称会被写入流。默认情况下,BinaryFormatter会输出程序集的完整标识符,其中包含程序集的文件名(无扩展名),版本号,语言文化以及公钥信息。反序列化一个对象向时,格式化器首先获取程序集标识信息,并通过调用Assembly的Load方法,确保程序集以加载到正在执行的AppDomain。
程序集加载之后,格式化器在程序集中查找与要反序列化的对象匹配的一个类型。如果程序集不包含一个匹配的类型,就抛出一个异常,不再对更多的对象序列化。如果找到一个匹配的类型,就创建类型的一个实例,并用流中包含的值对其字段进行初始化。如果类型中的字段与流中读取的字段名不完全匹配,就抛出一个异常,不再对更多的对象进行序列化。