2010-04-26 32 views
3

我正在編寫我自己的IFormatter實現,我想不出一種方法來解決實現ISerializable的兩種類型之間的循環引用。解決實現ISerializable的對象的循環引用

下面是通常的模式:

[Serializable] 
class Foo : ISerializable 
{ 
    private Bar m_bar; 

    public Foo(Bar bar) 
    { 
     m_bar = bar; 
     m_bar.Foo = this; 
    } 

    public Bar Bar 
    { 
     get { return m_bar; } 
    } 

    protected Foo(SerializationInfo info, StreamingContext context) 
    { 
     m_bar = (Bar)info.GetValue("1", typeof(Bar)); 
    } 

    public void GetObjectData(SerializationInfo info, StreamingContext context) 
    { 
     info.AddValue("1", m_bar); 
    } 
} 

[Serializable] 
class Bar : ISerializable 
{ 
    private Foo m_foo; 

    public Foo Foo 
    { 
     get { return m_foo; } 
     set { m_foo = value; } 
    } 

    public Bar() 
    { } 

    protected Bar(SerializationInfo info, StreamingContext context) 
    { 
     m_foo = (Foo)info.GetValue("1", typeof(Foo)); 
    } 

    public void GetObjectData(SerializationInfo info, StreamingContext context) 
    { 
     info.AddValue("1", m_foo); 
    } 
} 

然後我做到這一點:

Bar b = new Bar(); 
Foo f = new Foo(b); 
bool equal = ReferenceEquals(b, b.Foo.Bar); // true 

// Serialise and deserialise b 

equal = ReferenceEquals(b, b.Foo.Bar); 

如果我使用一個不折不扣的現成的BinaryFormatter序列化並deserialise B,上述測試正如人們所期望的那樣,引用相等返回true。但我無法想象在我的自定義IFormatter中實現這一點的方法。

在非ISerializable的情況下,我可以簡單地在解決目標引用後使用反射重新訪問「待處理」對象字段。但是對於實現ISerializable的對象,使用SerializationInfo注入新數據是不可能的。

任何人都可以指向正確的方向嗎?

回答

0

您需要檢測您在對象圖中多次使用同一個對象,在輸出中標記每個對象,並且當您出現#2或更高級別時,需要輸出「reference」一個現有的標籤而不是對象。序列化

僞代碼:反序列化

for each object 
    if object seen before 
     output tag created for object with a special note as "tag-reference" 
    else 
     create, store, and output tag for object 
     output tag and object 

僞代碼:

while more data 
    if reference-tag to existing object 
     get object from storage keyed by the tag 
    else 
     construct instance to deserialize into 
     store object in storage keyed by deserialized tag 
     deserialize object 

,你在那裏做最後的步驟順序他們指定的,所以這一點很重要你可以正確處理這種情況:

SomeObject obj = new SomeObject(); 
obj.ReferenceToSomeObject = obj; <-- reference to itself 

ie。在完全反序列化後,您無法將該對象存儲到您的標記存儲器中,因爲您可能需要在存儲器中對其進行反序列化時對其進行引用。

+0

我明白你關於「參考標籤」的觀點,我的格式化工具已經使用了這種技術。因此你的自引用例子對我來說不是問題。但是我沒有看到你的答案如何幫助我實現可互相引用的ISerializable-實現對象。你能解決這個具體問題嗎?謝謝。 – Chris 2010-04-26 10:55:29

+0

不完全確定你的意思。你在談論使用與序列化相關的私有構造函數嗎? – 2010-04-26 10:56:20

+0

是的,確切地說。膨脹實現ISerializable的對象的唯一方法是調用它的特殊構造函數。 – Chris 2010-04-26 11:01:27

5

這種情況是FormatterServices.GetUninitializedObject方法的原因。總的想法是,如果你有對象A和B相互引用他們SerializationInfo,你可以反序列化它們如下:

(對於這種解釋的目的,(SI,SC)是指一個類型的反序列化的構造,即一個它需要一個SerializationInfo和一個StreamingContext。)

  1. 選擇一個對象來先反序列化。只要你不選擇一種價值型的產品,你應該選擇哪一種產品。假設您選擇A.
  2. 請致電GetUninitializedObject分配(但不初始化)A類型的實例,因爲您尚未準備好調用其構造函數(SI,SC)
  3. 以通常的方式構建B,即創建一個SerializationInfo對象(它將包括對現在半解串行化的A的引用)並將其傳遞給B的(SI,SC)構造函數。
  4. 現在你擁有了你需要的所有依賴項初始化你分配的一個對象。創建它的SerializationInfo對象並調用A的(SI,SC)構造函數。您可以通過反射在現有實例上調用構造函數。

GetUninitializedObject方法是純粹的CLR魔術 - 它創建一個實例,而不用調用構造函數來初始化該實例。它基本上將所有字段設置爲零/空。

這就是您被告誡不要在構造函數(SI,SC)中使用子對象的任何成員的原因 - 子對象可能被分配但尚未初始化。這也是IDeserializationCallback接口的原因,它使您有機會在所有對象初始化完成後並且在返回反序列化對象圖之前使用您的子對象。

ObjectManager類可以爲你做所有這些(和其他類型的修復)。但是,考慮到反序列化的複雜性,我總是發現它的記錄不完整,所以我從來沒有花時間去嘗試弄清楚如何正確使用它。它使用一些更多的魔法來做第4步,使用一些優化的CLR反射來快速調用(SI,SC)構造函數(我的計時約爲公開方式的兩倍)。

最後,有些對象圖涉及無法反序列化的循環。一個例子是當你有一個循環的兩個IObjectReference實例(我已經測試了BinaryFormatter,並引發異常)。另一個我懷疑是,如果你有一個cycle involving nothing but boxed value-types

相關問題