2016-11-08 102 views
1

爲了實踐的緣故,我們假設我想實現一個序列化器(C#),並且我希望所述序列化器不會在循環引用上失敗。使用循環引用序列化對象圖的方法

明顯的解決辦法是隻序列化尚未遇到的對象並跳過對象。這很容易通過對實例進行散列來完成(以這種或那種方式)。

建議的解決方案出價問題:「什麼定義對象的身份?」人們會說 - 把它留給GetHashCode和Equals方法。 這是一個可接受的解決方案,它節省了序列化時間,並在反序列化時節省內存。

但是,這並不總是一個理想的結果,因爲許多實例可能具有相同的身份,但在序列化域中用於完全不同的事物,因此將它們解序列化,因爲同一實例會違反域邏輯。

所以,作爲這樣一個序列化程序的作者,我必須留給調用者做出這樣的決定。

解決此問題的一種方法是對每個所述類型散列集合,並通過迭代集合並在每個包含元素上調用ReferenceEquals來區分序列化和非序列化實例。 這可以工作,但不是最佳的 - 性能明智。

另一種方法是在非託管堆中固定對象,並使用固定對象地址作爲標識,這看起來有點矯枉過正並且還有很多開銷。

另一種方法是使用反射來調用每個實例的Object.Equals和Object.GetHashCode默認實現 - 這似乎解決了這個問題,但是它有一點小的開銷。

我的問題是:

1)是否有,我已經錯過了,我建議的方法什麼注意事項?
2)有沒有更多的額外方法,我可能沒有想到?

回答

1

看看System.Runtime.Serialization.ObjectIDGenerator。它確實如此。

按照MSDN頁:

使用哈希表,所述保留ObjectIDGenerator其ID被分配給哪個對象。唯一標識每個對象的對象引用是運行時垃圾回收堆中的地址。在序列化過程中,對象引用值可能會更改,但該表會自動更新,因此信息是正確的。

源代碼也可用here

0

實際上會導致循環引用(即應用程序中的無限循環)的唯一事情是實際的對象引用。所以不要保留散列列表,保留先前遇到的對象列表。

如果你想保持序列化的數據儘可能小,你可以實現它類似於nuget如何組織package文件夾 - 將每個對象寫出一次,但是當一個對象引用另一個對象時,編寫某種引用鍵。

[ 
    { 
     serialisationKey: "GUID1", 
     name: "Neil", 
     friends: [ 
      { obj: "GUID2" }, 
      { obj: "GUID3" } 
     ] 
    }, 
    { 
     serialisationKey: "GUID2", 
     name: "Bob", 
     friends: [ 
      { obj: "GUID1" } 
     ] 
    }, 
    { 
     serialisationKey: "GUID3", 
     name: "Alf", 
     friends: [ 
      { obj: "GUID1" } 
     ] 
    } 
] 
+0

你建議我使用第一種方法。我重視你的意見,但它不回答我的兩個問題。或者我錯過了什麼? –

0

不要釘到內存!你可以使用object.ReferenceEquals

你的序列化程序不應該很聰明,並試圖找出是否需要將相同的對象序列化爲一個對象或兩個。序列化每個對象一次 - 如果該對象被引用兩次,則在序列化數據中引用它兩次。

+0

你建議我使用第一種方法。我重視你的意見,但它不回答我的兩個問題。或者我錯過了什麼? –

相關問題