DeSerialization callback and member objects

K

ktrvnbq02

Hi,

I have a serializable class that has a NameValueCollection as a member
object. As part of the deserialization process, via a callback during
deserialization, the class needs to call various methods on the
NameValueCollection. This is currently failing due to the internal
state of NameValueCollection not being available, despite having a
reference to the object.

I have tried default serialization and implementing custom
serialization via ISerializable. I have also tried implementing the
callback using IDeserializationCallback and via the OnDeserialized
attribute in .NET 2.0.

I have simplified the test case down to the following, and I'd be
grateful for any comments as to why it does not work. Note that if I
comment out PrintCount() in the OnDeserialization method, the call in
Main() does succeed.

---------------------8<---------------------

using System;
using System.Collections;
using System.Collections.Specialized;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace Testcase
{
class Program
{
static void Main(string[] args)
{
try
{
NVCWrapper nvcWrapper = new NVCWrapper();

// Serialize
MemoryStream memoryStream = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(memoryStream, nvcWrapper);

// Deserialize
memoryStream.Seek(0, SeekOrigin.Begin);
NVCWrapper dsNvcWrapper = (NVCWrapper)
formatter.Deserialize(memoryStream);

// Print count
dsNvcWrapper.PrintCount();
}
catch (Exception ex)
{
Console.Write("Exception:");
while (ex != null)
{
Console.WriteLine(Environment.NewLine);
Console.WriteLine(ex.GetType().ToString());
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
ex = ex.InnerException;
}
}
finally
{
Console.WriteLine("Press a key to exit...");
Console.Read();
}
}
}

[Serializable]
public sealed class NVCWrapper : IDeserializationCallback
{
private NameValueCollection mNVC = new NameValueCollection();

public NVCWrapper()
{
mNVC.Add("TestName", "TestValue");
}

public void PrintCount()
{
Console.WriteLine("Count = " + mNVC.Count.ToString());
}

#region IDeserializationCallback Members

public void OnDeserialization(object sender)
{
PrintCount(); // NullReferenceException thrown from
NameValueCollection's Count property.
}

#endregion
}
}

---------------------8<---------------------


Now, if I swap the implementation of NameValueCollection for the
following stub class (updating the inline instantiation of
NameValueCollection to MyNameValueCollection in NVCWrapper to point to
the new class), the deserialization of NVCWrapper succeeds and no
exception is thrown in OnDeserialization:


---------------------8<---------------------

[Serializable]
public sealed class MyNameValueCollection
{
ArrayList mNames = new ArrayList();
ArrayList mValues = new ArrayList();

public MyNameValueCollection()
{
}

public void Add(string name, string val)
{
mNames.Add(name);
mValues.Add(val);
}

public int Count
{
get { return mNames.Count; }
}
}

---------------------8<---------------------


I'm just trying to understand what is wrong with the original test
case, and under what circumstances (if any) you can rely on member
objects being fully instantiated themselves when in a deserialization
callback.



Regards,

Matt
 
K

ktrvnbq02

Does anyone have any thoughts on this? I'd be really grateful for any
explanation as to why this test case fails.


Regards,

Matt

Hi,

I have a serializable class that has a NameValueCollection as a member
object. As part of the deserialization process, via a callback during
deserialization, the class needs to call various methods on the
NameValueCollection. This is currently failing due to the internal
state of NameValueCollection not being available, despite having a
reference to the object.

I have tried default serialization and implementing custom
serialization via ISerializable. I have also tried implementing the
callback using IDeserializationCallback and via the OnDeserialized
attribute in .NET 2.0.

I have simplified the test case down to the following, and I'd be
grateful for any comments as to why it does not work. Note that if I
comment out PrintCount() in the OnDeserialization method, the call in
Main() does succeed.

---------------------8<---------------------

using System;
using System.Collections;
using System.Collections.Specialized;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace Testcase
{
class Program
{
static void Main(string[] args)
{
try
{
NVCWrapper nvcWrapper = new NVCWrapper();

// Serialize
MemoryStream memoryStream = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(memoryStream, nvcWrapper);

// Deserialize
memoryStream.Seek(0, SeekOrigin.Begin);
NVCWrapper dsNvcWrapper = (NVCWrapper)
formatter.Deserialize(memoryStream);

// Print count
dsNvcWrapper.PrintCount();
}
catch (Exception ex)
{
Console.Write("Exception:");
while (ex != null)
{
Console.WriteLine(Environment.NewLine);
Console.WriteLine(ex.GetType().ToString());
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
ex = ex.InnerException;
}
}
finally
{
Console.WriteLine("Press a key to exit...");
Console.Read();
}
}
}

[Serializable]
public sealed class NVCWrapper : IDeserializationCallback
{
private NameValueCollection mNVC = new NameValueCollection();

public NVCWrapper()
{
mNVC.Add("TestName", "TestValue");
}

public void PrintCount()
{
Console.WriteLine("Count = " + mNVC.Count.ToString());
}

#region IDeserializationCallback Members

public void OnDeserialization(object sender)
{
PrintCount(); // NullReferenceException thrown from
NameValueCollection's Count property.
}

#endregion
}
}

---------------------8<---------------------


Now, if I swap the implementation of NameValueCollection for the
following stub class (updating the inline instantiation of
NameValueCollection to MyNameValueCollection in NVCWrapper to point to
the new class), the deserialization of NVCWrapper succeeds and no
exception is thrown in OnDeserialization:


---------------------8<---------------------

[Serializable]
public sealed class MyNameValueCollection
{
ArrayList mNames = new ArrayList();
ArrayList mValues = new ArrayList();

public MyNameValueCollection()
{
}

public void Add(string name, string val)
{
mNames.Add(name);
mValues.Add(val);
}

public int Count
{
get { return mNames.Count; }
}
}

---------------------8<---------------------


I'm just trying to understand what is wrong with the original test
case, and under what circumstances (if any) you can rely on member
objects being fully instantiated themselves when in a deserialization
callback.



Regards,

Matt
 
M

Marc Gravell

I suspect it is because (by implementing that interface) the deserializer
thinks you are taking full responsibility for deserialization; all you need
to do is:

public void OnDeserialization(object sender)
{
mNVC.OnDeserialization(sender);
PrintCount(); // NullReferenceException thrown from
NameValueCollection's Count property.
}

In 2.0, you could (instead of the interface) mark a method as
[OnDeserialized]; this would then fire *immediately after* deserialization,
without frigging things up.

Marc
 
M

Marc Gravell

Revised reasoning: it looks like the child (which also supports this
callback) simply hasn't had its callback invoked yet, and depends on it to
populate itself. Luckily, the callback on NameValueCollection looks to be
well-written to discard multiple calls, so you can get away with calling it
during the parent's callback.

Marc
 
K

ktrvnbq02

Marc said:
Revised reasoning: it looks like the child (which also supports this
callback) simply hasn't had its callback invoked yet, and depends on it to
populate itself. Luckily, the callback on NameValueCollection looks to be
well-written to discard multiple calls, so you can get away with calling it
during the parent's callback.

Thank you -- that does seem to be the cause of the issue, and manually
invoking OnDeserialize on the NameValueCollection does indeed solve the
problem in the test case.

I'll try to do some more investigation regarding the ordering
Deserialization callbacks, since this type of behaviour could well
produce some very subtle side-effects. Especially as in .NET 2.0 you
can simply apply the [OnDeserialized] attribute to a non-public method,
instead of implementing OnDeserializationCallback, leaving no way of
invoking it externally (even assuming programmers would guarantee the
method was safe to call multiple times).

Thanks again for your help, Marc.


Matt
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top