(反)序列化基礎
序列化
將對象(對象圖)轉換成字節流的過程
反序列化
對字節流轉換成對象(對象圖)的過程
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返回出去
格式化器檢查流的內容,構造流中所有對象的實例,並將這些對象的字段初始化,使它們具有與序列化時相同的值
如果將多個對象圖序列化到一個流中,只要反序列化時的順序正確,就可以把這些對象按序列化時的順序反序列化出來
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方法
該方法遍歷數組,將每個字段初始化成對應的值
調用對象中標記了「OnDeserialized」特性的所有方法
在反序列化期間,如果格式化器看到有方法標記了「OnDeserialized」特性,其會將該對象引用添加到一個內部列表中
所有對象都反序列化之後,格式化器反向遍歷列表,調用每個OnDeserialized的方法
流上下文
在某些情況下,一個對象可能需要知道它要在甚麼地方反序列化,可以通過StreamingContext來指定。StreamingContext包含兩個字段
StreamingContextStates:一組位標志,判斷(反)序列化的(目的)來源地
Object:包含用戶希望的任何上下文信息
參考書目
- 《CLR via C#》(第4版) Jeffrey Richter