Home All Groups Group Topic Archive Search About

DeSerialization callback and member objects

Author
14 Aug 2006 3:37 PM
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

Author
16 Aug 2006 9:26 AM
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

ktrvnb***@sneakemail.com wrote:
Show quote
> 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
Author
16 Aug 2006 9:26 AM
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

ktrvnb***@sneakemail.com wrote:
Show quote
> 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
Author
16 Aug 2006 9:54 AM
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
Author
16 Aug 2006 9:54 AM
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
Author
16 Aug 2006 10:08 AM
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
Author
16 Aug 2006 10:08 AM
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
Author
16 Aug 2006 11:22 AM
ktrvnbq02
Marc Gravell wrote:
> 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
Author
16 Aug 2006 11:22 AM
ktrvnbq02
Marc Gravell wrote:
> 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

AddThis Social Bookmark Button