首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

.NET 陷阱:IDeserializationCallback 回呼順序導致反序列化邏輯失效

     編輯:關於.NET

現象:反序列化回呼中,字典內容為空

類別 Example 實作 IDeserializationCallback,並在 OnDeserialization 中遍歷成員字典 map 輸出內容。預期:反序列化後立即輸出字典鍵值對。實際:**OnDeserialization 執行時,map 尚未重建完成,遍歷無輸出。**

根因:物件圖中多個 IDeserializationCallback 的調用順序不確定

Dictionary<TKey, TValue> 內部實作 IDeserializationCallback,其 OnDeserialization 負責從 SerializationInfo 重建雜湊表 (buckets, entries)。當 Example 被反序列化時,物件圖包含兩個實作該介面的物件:Example 本身與 Example.map。.NET 序列化器**不保證調用順序**,實測下 Example.OnDeserialization 先於 Dictionary.OnDeserialization 執行,導致邏輯依賴失效。

記憶體配置示意

GC 壓縮示意

解決方案:實作 ISerializable 手動控制序列化流程

放棄依賴 Dictionary 自動序列化,改由外層類別 Example 完整接管:

  1. 標記 map 為 [NonSerialized],阻止預設序列化。
  2. 實作 ISerializable.GetObjectData:將字典鍵值對拆解為兩個陣列 (keys[], vals[]) 寫入 SerializationInfo。
  3. 實作序列化建構函式 Example(SerializationInfo, StreamingContext):從 SerializationInfo 讀取陣列,**立即重建字典** (而非等待回呼)。
  4. OnDeserialization 保留為空或僅做後續初始化,此時字典已就緒。

修正後程式碼核心片段

[Serializable]
public class Example : ISerializable, IDeserializationCallback {
    [NonSerialized] private Dictionary<string, string> _map = new Dictionary<string, string>();

    public Example() { _map.Add("one", "1"); _map.Add("two", "2"); }

    // 序列化建構函式:反序列化時優先調用,此處主動重建字典
    protected Example(SerializationInfo info, StreamingContext context) {
        var keys = (string[])info.GetValue("MapKeys", typeof(string[]));
        var vals = (string[])info.GetValue("MapVals", typeof(string[]));
        _map = new Dictionary<string, string>();
        for (int i = 0; i < keys.Length; i++) _map.Add(keys[i], vals[i]);
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context) {
        var keys = new string[_map.Count];
        var vals = new string[_map.Count];
        int i = 0;
        foreach (var kv in _map) { keys[i] = kv.Key; vals[i] = kv.Value; i++; }
        info.AddValue("MapKeys", keys);
        info.AddValue("MapVals", vals);
    }

    public void OnDeserialization(object sender) {
        // 此時 _map 已在建構函式中重建完成,可安全使用
        Dump(); 
    }
    // ...
}

核心原則總結

若外層類別實作 IDeserializationCallback,且其回呼邏輯依賴某個成員物件,必須檢查該成員是否也實作 IDeserializationCallback。若是,外層類別必須改實作 ISerializable,在序列化建構函式中主動重建該成員,切斷對回呼順序的依賴。

延伸適用場景

  • 成員為 List<T>、HashSet<T>、SortedDictionary<TKey,TValue> 等集合類型 (均實作 IDeserializationCallback)。
  • 自訂類別同時實作 ISerializable 與 IDeserializationCallback (雙重保險模式) 時的順序衝突。
  • 版本容錯序列化:建構函式中可加入版本檢查、預設值填充等彈性邏輯。

總結

IDeserializationCallback 看似方便的「反序列化後統一初始化」機制,實則隱藏著物件圖拓樸順序的不確定性陷阱。掌握 ISerializable 序列化建構函式的**優先調用特權**,在建構階段主動重建依賴成員,是解決此類時序衝突的根本之道。這也提醒我們:序列化不僅是資料持久化,更是物件圖重建的精密協同過程,需對框架內部機制有清鍾認知。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved