C#筆記 – 序列化與反序列化
(反)序列化基礎
-
序列化
- 將對象(對象圖)轉換成字節流的過程
-
反序列化
- 對字節流轉換成對象(對象圖)的過程
-
static void Main(string[] args) { List<string> objGraph = new List<string> { "Apple", "Orange", "Banana" }; Stream stream = SerializeToMemory(objGraph); stream.Position = 0; objGraph = null; objGraph = DeserializeFromMemory(stream) as List<string>; foreach (var item in objGraph) { Console.WriteLine(item); } } //Serialize static MemoryStream SerializeToMemory(object objectGraph) { MemoryStream stream = new MemoryStream(); BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(stream, objectGraph); return stream; } //Deserialize static object DeserializeFromMemory(Stream stream) { BinaryFormatter formatter = new BinaryFormatter(); return formatter.Deserialize(stream); }- 使用格式化器:BinaryFormatter,進行(反)序列化的工作
- 序列化時,接受一個流引用,和一個序列化對象,把序列化對象序列化後的字節放在流引用中
- 格式化器利用反射查看對象類型中的實例字段,如果引用了其他對象,這些對象也要進行序列化
- 格式化器會確保每個對象只序列化一次
- 格式化器利用反射查看對象類型中的實例字段,如果引用了其他對象,這些對象也要進行序列化
- 反序列化時,接受一個流引用,將流引用裡的字節反序列化後,把一個對象object返回出去
- 格式化器檢查流的內容,構造流中所有對象的實例,並將這些對象的字段初始化,使它們具有與序列化時相同的值
- 序列化時,接受一個流引用,和一個序列化對象,把序列化對象序列化後的字節放在流引用中
- 使用格式化器:BinaryFormatter,進行(反)序列化的工作
-
如果將多個對象圖序列化到一個流中,只要反序列化時的順序正確,就可以把這些對象按序列化時的順序反序列化出來
-
List<int> intTarget_Single = new List<int> { 1, 3, 5, 7, 9 }; List<string> stringTarget = new List<string> { "Hello", "World", "!" }; List<int> intTarget_Double = new List<int> { 2, 4, 6, 8, 10 }; MemoryStream ms = new MemoryStream(); BinaryFormatter bf = new BinaryFormatter(); bf.Serialize(ms, intTarget_Single); bf.Serialize(ms, stringTarget); bf.Serialize(ms, intTarget_Double); ms.Position = 0; //序列化後,重置流的下標,才能重新以正確的順序反序列化 List<int> resultA = new List<int>(); List<string> resultB = new List<string>(); List<int> resultC = new List<int>(); resultA = (List<int>)bf.Deserialize(ms); resultB = (List<string>)bf.Deserialize(ms); resultC = (List<int>)bf.Deserialize(ms); foreach (var a in resultA) { Console.WriteLine(a); } foreach (var b in resultB) { Console.WriteLine(b); } foreach (var c in resultC) { Console.WriteLine(c); }
-
-
序列化對象的時候,類型的全名和定義類型的程序集全名會被寫入流,BinaryFormatter默認輸出程序集的完整標識
- 反序列化對象時,格式化器獲得該完整標識,調用Assembly.Load將程序集加載到AppDomain中
- 加載好後,格式化器在程序集中查找與要反序列化對象匹配的類型,找到之後創建類型的實例,並用流中包含的值對其字段進行初始化
(反)序列化相關特性
-
System.Serializable
- 類型默認是不可序列化的,需要顯式聲明該特性
- 該特性適用於引用類型、值類型、枚舉類型、委托類型
- 枚舉和委托總是可序列化的,不需要顯式指定該特性
- 該特性不會被派生類所繼承
-
[System.Serializable] public class SerializeClass { }
-
System.NonSerialized
-
指出可序列化類型中不應被序列化的字段
-
可減少需要傳輸的數據,提升性能
-
[Serializable] public class SerializeClass { double radius; [NonSerialized] double area; public SerializeClass(double radius) { this.radius = radius; area = Math.PI * this.radius * this.radius; } } -
序列化時,只有radius字段的值會被寫入流;反序列化時,radius的值會被正常初始化為序列化時的值,而area由於沒有序列化,所以值會被初始化為0
-
-
Serialization.OnSerializing
-
[OnSerializing] void OnSerializing(StreamingContext context) { } -
序列化前調用
-
-
Serialization.OnSerialized
-
[OnSerialized] void OnSerialized(StreamingContext context) { } -
序列化後調用
-
-
Serialization.OnDeserializing
-
[OnDeserializing] void OnDeserializing(StreamingContext context) { } -
反序列化前調用
-
-
Serialization.OnDeserialized
-
[OnDeserialized] void OnDeserialized(StreamingContext context) { } -
每次反序列化類型的實例後,格式化器會檢查類型中是否定義了應用了該特性的方法,如果是,就調用該方法
- 該方法調用時,所有可序列化的字段都已經被正確設置
-
-
OptionalField
- 防止試圖反序列化不包含新字段的對象時,格式化器拋出異常
(反)序列化過程
- 序列化一組對象時,格式化器:
- 首先調用對象中標記了「OnSerializing」特性的所有方法
- 序列化對象的所有字段
- 格式化器調用FormatterServices的GetSerializableMembers方法,獲取類型的公有/私有字段(不包含有NonSerialized特性的),返回MemberInfo數組
- 對象被序列化,MemberInfo數組傳給FormatterServices的GetObjectData方法
- 該方法返回一個Object數組,每個元素都標識了被序列化的那個對象中的一個字段的值
- 該Object數組與MemberInfo數組是并行的
- 格式化器將程序集標識和類型全名寫入流中
- 格式化器遍歷兩個數組,將每個成員名稱和值寫入流中
- 調用對象中標記了「OnSerialized」特性的所有方法
- 反序列化時,格式化器:
- 首先調用對象中標記了「OnDeserializing」特性的所有方法
- 反序列化對象的所有字段
- 格式化器從流中讀取程序集標識和完整類名,嘗試將其加載至當前AppDomain,並將程序集標識信息和完整類名傳給FormatterServices的GetTypeFromAssembly方法
- 該方法返回一個Type對象,代表要反序列化的對象的類型
- 格式化器調用FormatterServices的GetUninitializedObject方法
- 該方法為新對象分配內存,但不調用其構造器,所有字段會被初始化為0/null
- 格式化器調用FomatterServices的GetSerializableMembers方法構造並初始化一個MemberInfo數組
- 返回序列化好,需要反序列化的一組字段
- 格式化器根據流中包含的數據創建並初始化一個Object數組
- 將新分配對象、MemberInfo數組和Object數組的引用傳給FormatterServices的PopulateObjectMembers方法
- 該方法遍歷數組,將每個字段初始化成對應的值
- 格式化器從流中讀取程序集標識和完整類名,嘗試將其加載至當前AppDomain,並將程序集標識信息和完整類名傳給FormatterServices的GetTypeFromAssembly方法
- 調用對象中標記了「OnDeserialized」特性的所有方法
- 在反序列化期間,如果格式化器看到有方法標記了「OnDeserialized」特性,其會將該對象引用添加到一個內部列表中
- 所有對象都反序列化之後,格式化器反向遍歷列表,調用每個OnDeserialized的方法
流上下文
- 在某些情況下,一個對象可能需要知道它要在甚麼地方反序列化,可以通過StreamingContext來指定。StreamingContext包含兩個字段
- StreamingContextStates:一組位標志,判斷(反)序列化的(目的)來源地
- Object:包含用戶希望的任何上下文信息
參考書目
- 《CLR via C#》(第4版) Jeffrey Richter