標籤:
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。
程式集載入之後,格式化器在程式集中尋找與要還原序列化的對象匹配的一個類型。如果程式集不包含一個匹配的類型,就拋出一個異常,不再對更多的對象序列化。如果找到一個匹配的類型,就建立類型的一個執行個體,並用流中包含的值對其欄位進行初始化。如果類型中的欄位與流中讀取的欄位名不完全符合,就拋出一個異常,不再對更多的對象進行序列化。
第一節:序列化和還原序列化快速入門