[討論] Serialize的問題

看板C_Sharp (C#)作者 (Op穎)時間20年前 (2005/06/21 22:02), 編輯推噓3(303)
留言6則, 3人參與, 最新討論串1/1
Serialization 或稱 Persistance 是一種將物件保存的機制,透過這套機制 可以將物件保存在檔案中,再需要時再還原物件的狀態。 .NET有直接支援Serialize,任何class只要標記[Serializable]就具備了 Serialize的能力,所有的Fiels(public, private, protected, static..) 都會在程式呼叫IFormatter.Serialize()的時候被保存下來,在程式呼叫 IFormatter.Deserialize()時從stream中建立class的instance。 關於這些基本的概念MSDN文件上都有相當詳細的說明,這邊就簡單的帶過 [Serializable] SerializableAttribute class的簡寫 [AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Delegate, Inherited=false)] public sealed class SerializableAttribute : Attribute 由上面的宣告我們可以知道[Serializable]對象為 1.Class 2.Struct 3.Enum 4.Delegate 且沒有繼承的效果(Inherited = false),這點很重要稍後會討論。 一般而言標示了[Serializable]通常不用任何處理就可以完成大部分 Serialize的工作了。 [NonSerialized] NonSerializedAttribute class的簡寫用來指示Field不用 在執行Serialize時存到stream中。 ISerializable 用來實做自定的Serialize動作請直接參考MSDN的範例 在這邊要討論的是小弟不久前遇到的一個問題,首先把焦點放到之前提過的 [Serializable]這個屬性,由於這個屬性不具備繼承的能力( AttributeUsageAttribute(...,Inherited = false) ) 所以下面的程式碼會有問題 class MyBase { .... } [Serializable] class MyDerived : MyBase, ISerializable { public MyDerived(){} public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { ....... //處理MyDerived的Serialize動作 base.GateObjectData(info,context); //處理base的serialize ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 這一行會丟出SerializationException因為base沒有[Serializable] } protected MyDerived(SerializationInfo info, StreamingContext context) : base(info,context) ^^^^^^^^^throw Exception { ... //處理deserialize的動作 } } 有幾種方法可以解決 1.把程式碼中串聯到base的serialize動作拿掉 (base.GateObjectData(info,context); or base(info,context)) 但是假如哪天base又變成需要serialize是不是我們又要把這些被拿掉的程式碼 加回去? 2.將base也標上[Serializable] 問題是第一如果base真的不需要serialize這顯然會混淆其他的工程師。 第二 假如base我們沒有source code怎辦?叫提供base的廠商給我們加上 後再寄來? 上面兩種解法都會造成我門開發的class和base再處理serialize上的偶合(coupling) 也就是我們沒有辦法在base變更serialize的支援時不改變繼承體系其他的class程式。 程式有越多的偶合就越難維護,這當然要避免,這邊提供一個例子 class GenericBaseSerialize { public static void SerializeBase(SerializationInfo info, StreamingContext context, object obj) { Type baseType = obj.GetType().BaseType; //當baseType為object時結束deserialize while(baseType != typeof(object)) { BindingFlag flag = BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Public; //取得所有base type 的 Fields FieldInfo[] fields = baseType.GetFields(flag); foreach (FieldInfo field in fields) { if(field.IsNotSerialized) { //field不需要serialize繼續下一個field continue; } string sToken = type.Name + field.Name; info.AddValue(sToken, field.GetValue(obj)); } //繼續處理base type baseType = baseType.BastType; } } public static void DeSerializeBase(SerializationInfo info, StreamingContext context, object obj) { Type baseType = obj.GetType().BaseType; //當baseType為object時結束deserialize while(baseType != typeof(object)) { BindingFlag flag = BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Public; //取得所有base type 的 Fields FieldInfo[] fields = baseType.GetFields(flag); foreach (FieldInfo field in fields) { if(field.IsNotSerialized) { //field不需要serialize繼續下一個field continue; } string sToken = type.Name + field.Name; //取出field的值(deserialize) object value = info.GetValue(sToken,field.FieldType); //將取出的值設定到obj中的field field.SetValue(obj, value); } //繼續處理base type baseType = baseType.BastType; } } } [Serializable] class MyDerived : MyBase, ISerializable { public MyDerived(){} public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { ....... //處理MyDerived的Serialize動作 //處理base的serialize動作 GenericBaseSerialize.SerializeBase(info,context,this); } protected MyDerived(SerializationInfo info, StreamingContext context) { ... //處理deserialize的動作 //處理base的serialize動作 GenericBaseSerialize.DeSerializeBase(info,context,this); } } 完全不用再花心力去管base是否有支援[Serializable]了。 不過這個做法有一個缺點就是Reflection的動作實在是很慢,大量物件還是盡量避免 ...畢竟彈性和效率總是兩難全... -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 61.229.16.186

140.109.73.177 06/22, , 1F
頭推 p兄的文章每次都很有深度
140.109.73.177 06/22, 1F

140.125.251.33 06/22, , 2F
不考慮空間問題 我是都把object轉成xml儲存
140.125.251.33 06/22, 2F

211.75.23.122 06/22, , 3F
小弟說的大量物件是可能程式中會頻繁建立一個
211.75.23.122 06/22, 3F

211.75.23.122 06/22, , 4F
物件非常多次,如果這種情形每次serialize時
211.75.23.122 06/22, 4F

211.75.23.122 06/22, , 5F
都用上面方法對效率影響頗大
211.75.23.122 06/22, 5F

140.125.251.33 06/23, , 6F
之前是因為傳遞WebService的時候使用到:D
140.125.251.33 06/23, 6F
文章代碼(AID): #12k1th-u (C_Sharp)
文章代碼(AID): #12k1th-u (C_Sharp)