|
dev
newsgroups
|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
DeSerialization callback and member objectsI 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 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 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 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 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 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 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 Marc Gravell wrote:
> Revised reasoning: it looks like the child (which also supports this Thank you -- that does seem to be the cause of the issue, and manually> 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. 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 Marc Gravell wrote:
> Revised reasoning: it looks like the child (which also supports this Thank you -- that does seem to be the cause of the issue, and manually> 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. 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 |
|||||||||||||||||||||||