CLR via C#:運行時序列化

特殊聲明:如下所示:
1.本博客中的"特殊構造函數"指的是具有SerializationInfo和StreamingContext類型參數的構造函數。

基礎知識:如下所示:
1.對象圖是一個抽象的概念,代表的是對象系統在特定時間點的一個視圖。
2.序列化是將對象圖轉換成字節流的過程。
3.反序列化是將字節流轉換成對象圖的過程。
4.XmlSerializer和DataContractSerializer類可以將XML進行序列化和反序列化。
5.BinaryFormatter格式化器可以將對象圖進行序列化和反序列化。
6.可以將多個對象圖序列化到一個字節流中,然後從該字節流中反序列化成多個對象圖。
7.進行序列化操作時,當前操作的對象不能被序列化時,就會拋出SerializationException。
8.SerializationInfo類的SetType函數用來設置真實要序列化的對象。
9.類型實現了IObjectReference的GetRealObject函數時,該函數會在反序列化字節流後才被調用,從而返回真實的類型對象。
10.代理選擇器鏈是將代理選擇器對象鏈接在一起的技術。代理選擇器對象可以通過ISurrogateSelector.ChainSelector函數來將新的代理選擇器對象添加到鏈尾;通過ISurrogateSelector.GetNextSelector函數來獲取下一個代理選擇器對象;通過ISurrogateSelector.GetSurrogate函數來獲取指定類型引用對象和流上下文對象的代理選擇器對象。

格式化器以反射的方式序列化對象:如下所示:
1.類型以及該類型關聯的基類型都必須應用SerializableAttribute定製特性。
2.類型的字段應用NonSerializedAttribute定製特性時,該字段纔可以不被序列化。
3.類型中的函數應用OnSerializingAttribute定製特性時,該函數會在序列化對象前調用。
4.執行序列化對象的流程如下所示:
1>.調用FormatterServices的GetSerializableMembers函數來獲取MemberInfo數組(可序列化字段信息數組)。
2>.調用FormatterServices的GetObjectData函數來獲取Object數組(可序列化字段數值數組)。
3>.將程序集標識和類型的完整名稱寫入字節流中。
4>.遍歷MemberInfo數組和Object數組,將每個可序列化字段的名稱和值寫入字節流中。
5.類型中的函數應用OnSerializedAttribute定製特性時,該函數會在序列化對象後調用。

格式化器以反射的方式反序列化字節流:如下所示:
1.類型的字段應用OptionalFieldAttribute定製特性時,當字節流中不存在該字段數據時不會拋出SerializationException。
2.類型中的函數應用OnDeserializingAttribute定製特性時,該函數會在反序列化字節流前調用。
3.執行反序列化字節流的流程如下所示:
1>.從字節流中讀取程序集標識和完整類型名稱。流程如下所示:
1>>.將程序集加載到AppDomain中,如果加載失敗就拋出SerializationException。
2>>.調用FormatterServices的GetTypeFromAssembly函數來獲取類型引用對象,如果獲取失敗就拋出SerializationException。
2>.調用FormatterServices的GetUninitializedObject函數來獲取不調用構造函數的類型對象。
3>.調用FormatterServices的GetSerializableMembers函數來獲取MemberInfo數組(可反序列化字段信息數組)。
4>.根據字節流中包含的數據創建並初始化一個Object數組(可反序列化字段數值數組)。
5>.調用FormatterServices的PopulateObjectMembers函數來將指定類型對象中的指定字段初始化成指定的數值。
4.類型中的函數應用OnDeserializedAttribute定製特性時,該函數會在反序列化字節流後調用。

格式化器以非反射的方式序列化對象:如下所示:
1.類型必須應用SerializableAttribute定製特性以及實現ISerializable的GetObjectData函數。注意以下幾點:
1>.派生類型沒有任何額外的字段時,可以不用實現ISerializable的GetObjectData函數;否則就必須實現ISerializable的GetObjectData函數,並在內部調用基類實現ISerializable的GetObjectData函數。
2>.爲了避免外界非法調用ISerializable的GetObjectData函數,可以對該函數應用[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter=true)]定製特性,從而讓該函數只被格式化器進行序列化對象時調用。
2.執行序列化對象的流程如下所示:
1>.構造SerializationInfo對象。
2>.將SerializationInfo對象作爲參數來調用ISerializable的GetObjectData函數。該函數內部會利用SerializationInfo對象的AddValue函數來將對象的字段寫入到字節流中。

格式化器以非反射的方式反序列化字節流:如下所示:
1.類型必須提供特殊構造函數以及實現IDeserializationCallback的OnDeserialization函數。注意以下幾點:
1>.派生類型沒有任何額外的字段時,可以不用提供特殊構造函數;否則就必須提供特殊構造函數,並在內部調用基類提供的特殊構造函數。
2>.爲了避免外界非法調用特殊構造函數,可以對該函數應用[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter=true)]定製特性,從而讓該函數只被格式化器進行反序列化字節流時調用;
2.執行反序列化字節流的流程如下所示:
1>.從SerializationInfo對象中讀取程序集標識和完整類型名稱。流程如下所示:
1>>.將程序集加載到AppDomain中,如果加載失敗就拋出SerializationException。
2>>.調用FormatterServices的GetTypeFromAssembly函數來獲取類型引用對象,如果獲取失敗就拋出SerializationException。
2>.調用FormatterServices的GetUninitializedObject函數來獲取不調用構造函數的類型對象。
3>.調用特殊構造函數來初始化類型對象,並存儲SerializationInfo對象。
4>.在IDeserializationCallback的OnDeserialization函數內部利用SerializationInfo對象調用GetX(X爲基元類型或者Value)函數或者GetEnumerator函數從字節流中獲取字段的值。注意以下幾點:
1>>.GetX函數嘗試獲取字段的類型和字節流中值的類型不符時,格式化器會使用SerializationInfo對象中的格式轉換對象調用Convert的ChangeType函數來返回一個IConvertible接口,然後再調用該接口的函數來實現類型轉換。

流上下文:類型爲StreamingContext,是一個非常簡單的值類型。具有以下特性:
1.StreamingContext的公共只讀屬性如下表所示:

成員名稱 成員類型 說明
State StreamingContextState 一組位標誌,指定要(反)序列化對象的來源或目的地
Context Object 額外狀態對象引用

2.StreamingContextState枚舉定義如下表所示:

標誌名稱 標誌值 說明
CrossProcess 0x0001 來源或目的地是同一臺機器的不同進程
CrossMachines 0x0002 來源或目的地在不同機器上
File 0x0004 來源或目的地是文件。不保證反序列化數據的是同一個進程
Persistence 0x0008 來源或目的地是存儲。不保證反序列化數據的是同一個進程
Remoting 0x0010 來源或目的地是遠程的未知位置。這個位置可能在(也可能不在)同一臺機器上
Other 0x0020 來源或目的地未知
Clone 0x0040 對象圖被克隆
CrossAppDomain 0x0080 來源或目的地是不同的AppDomain
All 0x00FF 來源或目的地可能是上面任何一個標誌,這也是默認的設定

3.格式化器中提供一個Context屬性來供開發人員提供合適的StreamingContext對象。

(反)序列化代理:用來重寫類型的(反)序列化行爲。以DateTime爲例,執行流程如下所示:
1>.定義代理類型。該代理類型實現ISerializationSurrogate的GetObjectData和SetObjectData函數來依次重寫序列化和反序列化行爲。參考僞代碼如下所示:

public sealed class DateTimeSurrogate : ISerializationSurrogate
{
	// obj代表序列化對象
	// info代表序(反)列化操作對象
	// context代表流上下文
	public void GetObjectData(Object obj, SerializationInfo info, StreamingContext context)
	{
		// 使用info對象的AddValue函數來將obj對象寫入到字節流中
		info.AddValue("Date", ((DateTime)obj).ToUniversalTime().ToString("u")); 
	}

	// obj是反序列化字節流時生成的對象
	// info代表序(反)列化操作對象
	// context代表流上下文
	// selector代表代理選擇器對象
	// 返回值:返回null等價於返回obj;返回其他對象等價於廢棄obj
	public Object SetObjectData(Object obj, SerializationInfo info, StreamingContext context, SurrogateSelector selector)
	{
		// 使用info對象的GetX(X爲基元類型或者Value)函數來獲取字節流中的值
		DateTime dt = DateTime.ParseExact(info.GetString("Date"), "u", null).ToLocalTime(); 
		#if 使用BinaryFormatter格式化器 && 處理循環引用對象
			// 必須修改obj對象中的值且最後要返回該obj對象
			FieldInfo fi = typeof(DateTime).GetField("dateData", BindingFlags.NonPublic | BindingFlags.Instance);
         	fi.SetValue(obj, fi.GetValue(dt));
         	return null;
        #else
        	return dt;
		#endif
	}
}

2>.創建格式化器對象。參考僞代碼如下所示:

IFormatter formatter = null; 
#if 使用BinaryFormatter格式化器
	formatter = new BinaryFormatter();
#else
	formatter = new SoapFormatter(); 
#endif

3>.創建代理選擇器對象。參考代碼如下:

SurrogateSelector ss = new SurrogateSelector();

4>.創建代理對象。其中BinaryFormatter格式化器對象在處理循環引用類型對象時,需要調用
FormatterServices.GetSurrogateForCyclicalReference函數來處理並返回代理對象。參考僞代碼如下所示:

ISerializationSurrogate s = new DateTimeSurrogate();
#if 使用BinaryFormatter格式化器 && 處理循環引用對象
	s = FormatterServices.GetSurrogateForCyclicalReference(s); 
#endif

5>.將類型引用對象,流上下文對象以及代理對象作爲參數來調用代理選擇器對象的AddSurrogate函數,從而將指定的類型在進行(反)序列化操作時,就調用代理對象中相關的函數進行重寫處理。參考代碼如下所示:

ss.AddSurrogate(typeof(DateTime), formatter.Context, s);

6>.告訴格式化器對象使用代理選擇器對象。參考代碼如下所示:

formatter.SurrogateSelector = ss;

7>.調用格式化器對象的Serialize函數時,格式化器對象會從代理選擇器對象中查找是否有跟當前要序列化類型相匹配的代理對象。當找到匹配的代理對象時,就調用該代理對象的GetObjectData函數來執行序列化對象操作。
8>.調用格式化器對象的Deserialize函數時,格式化器對象會從代理選擇器對象中查找是否有跟當前要反序列化類型相匹配的代理對象。當找到匹配的代理對象時,就調用該代理對象的SetObjectData函數來執行反序列化字節流操作。

(反)序列化綁定:用來將對象反序列化成不同類型。流程如下所示:
1.定義綁定器類型並實現SerializationBinder的BindToName和BindToType函數。參考僞代碼如下所示:

public sealed class MySerializationBinder : SerializationBinder
{
	// serializedType代表序列化的類型
	// 返回值:序列化對象時,更改程序集標識和類型全名
	public override void BindToName(Type serializedType, out String assemblyName, out String typeName)
	{
	}

	// assemblyName代表程序集標識
	// typeName代表類型全名
	// 返回值:反序列化字節流時返回的最終對象類型
	public override Type BindToType(String assemblyName, out String typeName)
	{
	}
}

2.將綁定器對象賦值給格式化器對象的Binder屬性。
3.調用格式化器對象的Serialize函數時,格式化器對象如果設置了綁定器屬性的話,就會調用綁定器對象的BindToName函數來獲取序列化對象時真正想要序列化的程序集和類型。
4.調用格式化器對象的Deserialize函數時,格式化器對象如果設置了綁定器屬性的話,就會調用綁定器對象的BindToType函數來獲取反序列化字節流時真正要生成的類型。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章