From Rockford Lhotka's Expert C# 2008 and VB 2008 Business Objects books
Hi,
i noticed a strange bug affecting the OnDeserializedHandler (with the OnDeserializedAttribute): when this handler is called, the items of some collections or dictionary objects are not yet deserialized. I ecountered this playing with the new Shared Validation Rules: some rules in my BO depend on a Dictionary(Of String, String) object, which was not properly populated in the handler (.Count = 0) . But inspecting the object after deserialization shows that all items are there. It looks like the OnDeserializedHandler is called too early.
Here is a simple console application to reproduce this bug:
Imports System.IOImports System.Runtime.Serialization.Formatters.BinaryImports System.Runtime.SerializationImports System.Collections.SpecializedImports System.ComponentModel
Module Module1
Sub Main()
Dim obj1 As New MySerializableClass obj1.Populate()
Using buffer As New MemoryStream() Dim formatter As New BinaryFormatter formatter.Serialize(buffer, obj1) buffer.Position = 0
obj1.Print(" before Serialization") Dim obj2 As MySerializableClass = _ DirectCast(formatter.Deserialize(buffer), MySerializableClass) obj2.Print(" after Deserialization")
End Using
End Sub
End Module
<Serializable()> _Public Class MySerializableClass Public genDic As Dictionary(Of String, String) Public lst As List(Of String) Public hashTbl As Hashtable Public hd, hdl As HybridDictionary Public sd As StringDictionary Public ld As ListDictionary Public sc As StringCollection Public sl As SortedList Public gensl As SortedList(Of String, String) Public gensd As Generic.SortedDictionary(Of String, String) Public bl As BindingList(Of String)
Public Sub Populate() genDic = New Dictionary(Of String, String) genDic.Add("foo", "bar") lst = New List(Of String) lst.Add("foo") hashTbl = New Hashtable hashTbl.Add("foo", "bar") hd = New HybridDictionary hd.Add("foo", "bar") hdl = New HybridDictionary For i As Integer = 1 To 10 hdl.Add(i, i) Next sd = New StringDictionary sd.Add("foo", "bar") ld = New ListDictionary ld.Add("foo", "bar") sc = New StringCollection sc.Add("foo") sl = New SortedList sl.Add("foo", "bar") gensl = New SortedList(Of String, String) gensl.Add("foo", "bar") gensd = New Generic.SortedDictionary(Of String, String) gensd.Add("foo", "bar") bl = New BindingList(Of String) bl.Add("foo") End Sub
<OnDeserialized()> _ Private Sub OnDeserializedHandler(ByVal context As StreamingContext) Me.Print(" in OnDeserializedHandler") End Sub
Public Sub Print(ByVal location As String) Print("small HybridDictionary : ", hd.Count, location) Print("ListDictionary : ", ld.Count, location) Print("StringCollection : ", sc.Count, location) Print("SortedList : ", sl.Count, location) Print("List(Of String) : ", lst.Count, location) Print("SortedList(Of String, String) : ", gensl.Count, location) Print("BindingList(Of String) : ", bl.Count, location)
'these have no items in OnDeserializedHandler: Print("Hashtable : ", hashTbl.Count, location) Print("large HybridDictionary : ", hdl.Count, location) Print("StringDictionary : ", sd.Count, location) Print("Dictionary(Of String, String) : ", genDic.Count, location) Print("SortedDictionary(Of String, String): ", gensd.Count, location)
Console.WriteLine() End Sub
Private Sub Print(ByVal s As String, ByVal iCount As Integer, _ ByVal location As String) Console.WriteLine(s & iCount.ToString & location) End Sub
End Class
Rocky
I think a problem I'm seeing is a result of this behavior, and wanted to see if people agree or if I'm off base:
Apparently, Dictionary(Of String, Object) falls into this not-available-in-OnDeserialized category. This has the (very unfortunate) effect of preventing me from accessing business object properties from the OnDeserialized handler. What seems to be happening is: the CanReadProperty() call in a property's Get method (after several levels of other calls) calls GetRolesForProperty, which tries to access the AuthorizationRules.Rules property, and fails with a NullReferenceException.
This is a very subtle and non-intuitive problem. (It's been driving me crazy for hours now.)
Does this sound reasonable or am I missing something else? Has anyone else run into this?
Thanks, Andy
Andy,
I just read this thread. Wow. What a repro from olafb.
As far as your problem goes, can you skip the Property Get/Set and use the member variable directly instead?
===================================
Rocky,
You win the bet - all the objects do implement ISerializable.
Can you please elaborate on the implications of this with respect to Andy's problem and other things we should be careful about while handling OnDesrialized? Also, do the calls that you make to OnDeserialized in the framework work in all cases? Or are there other problems we should be aware of?
Joe
Thanks everyone for the Info. For my current case, I'm now using a friend property that doesn't check rules. (I didn't want to expose the field directly.)
Rocky, could you elaborate on your statement that "by the time an object is created, all objects it depends on exist", please? Does this mean that when any of the objects in the graph's OnDeserialize method is called, all objects in the graph will at least exist, if not be fully initialized?
I ask because I have a model where some great-great-grandchildren of the base object have an interface reference to the base object. (It provides some services to these children.) For this to work if/when I clone all or part of the tree, each of the children needs to keep a reference to this interface, for instantiating their children, and so on down to the bottom, where the great-great grandchildren consume the interface. (This is loosely based on the CSLA Parent references.) My question is how I should propagate this down the tree.
I see two options: First, I could have each child have it's own OnDeserialize method that updates its children. But, do I know that the child's parent has updated the child yet? The other option seems to be to have each child's OnDeserialize call the base object and let it walk through its children, who in turn walk thier own children, etc. But, this seems like a lot of redundant calling.
Okay, here's what I decided: I'm overloading the Clone method in each of my child objects, so it calls the base Clone and then calls a SetInterface method on the clone, which in turn calls its children etc. That way I ensure that each of the child objects will exist.
This works fine in a 1-machine scenario, but I suspect I will have trouble if/when I start to use remoting, since the serialization/deserialization won't be done as a result of the Clone method call. I'm under some time pressure, so I guess I'll have to cross that bridge when I come to it. I'll keep you posted if I learn anything or have any brilliant insights.
Thanks for the help, Andy
One thing I forgot to mention. To override the Clone method, I had to change the Clone function signature in BusinessListBase.vb from:
Public Overloads Function Clone() As T
to
Public Overridable Function Clone() As T
Since this now matches the signature from BusinessBase.vb, is it safe to assume that the original signature was incorrect, or am I missing something?
From: AndrewCr [mailto:cslanet@lhotka.net] Sent: Wednesday, August 30, 2006 12:05 AMTo: rocky@lhotka.netSubject: Re: [CSLA .NET] MS-bug in OnDeserializedHandler? One thing I forgot to mention. To override the Clone method, I had to change the Clone function signature in BusinessListBase.vb from: Public Overloads Function Clone() As T to Public Overridable Function Clone() As T Since this now matches the signature from BusinessBase.vb, is it safe to assume that the original signature was incorrect, or am I missing something? Thanks, Andy
Will do.
From: xal [mailto:cslanet@lhotka.net] Sent: Wednesday, August 30, 2006 7:31 AMTo: rocky@lhotka.netSubject: Re: [CSLA .NET] RE: MS-bug in OnDeserializedHandler? Andy, have you tried calling OnDeserialization() in your dictionary??As I said in a previous post, that forces deserialization, and after that, you can use it inside your OnDeserialized()...AndrC)s