24

I use a simple class that is serializable. It has a constructor for the deserialization:

protected MyClass(SerializationInfo info, StreamingContext context)

and a GetObjectData method for serialisation. It works fine.

Now I added two methods to monitor the deserialisation:

        [OnDeserializing()]
    internal void OnDeserializingMethod(StreamingContext context)
    {
        System.Diagnostics.Trace.WriteLine("OnDeserializingMethod: " + this.GetType().ToString());
    }

    [OnDeserialized()]
    internal void OnDeserializedMethod(StreamingContext context)
    {
        System.Diagnostics.Trace.WriteLine("OnDeserializedMethod: " + this.GetType().ToString());
    }

and was wondering in which order these methods are beeing called. Now both methods get called before the constructor gets called. How is that possible, and why isn't the "OnDeserialized" method called after the (deserialization-) constructor has been called? And how can a (non-static) method be called before any constructor has been executed? (I am using a BinaryFormatter)

Gerhard
  • 1,342
  • 2
  • 12
  • 23
  • How did you managed to get these internal methods called ? I am trying this for some time and these methods just don't get called – GuidoG Jan 29 '21 at 11:04

3 Answers3

29

Now both methods get called before the constructor gets called

No, the order is:

  • OnDeserializingMethod
  • .ctor
  • OnDeserializedMethod

And how can a (non-static) method be called before any constructor has been executed?

Because it cheats and lies; it doesn't create the object with the constructor; no - really. It uses FormatterServices.GetUninitializedObject to allocate vanilla empty space. And then if there is a custom deserialization constructor it invokes the constructor over the top of that object. Nasty. Like this, basically:

var obj = FormatterServices.GetUninitializedObject(typeof(MyClass));
var ctor = obj.GetType().GetConstructor(
    BindingFlags.Instance | BindingFlags.Public| BindingFlags.NonPublic,
    null,
    new[] { typeof(SerializationInfo), typeof(StreamingContext) },
    null);
ctor.Invoke(obj, new object[2]);

IMO they probably should have made this a second method on the ISerializable interface, but for whatever reason: they didn't. A shame really: that would have made it more honest, and avoided people needing to remember to implement the custom constructor.

Example output:

.ctor: MyClass
> serializing
OnSerializingMethod: MyClass
GetObjectData: MyClass
OnSerializedMethod: MyClass
< serializing
> deserializing
OnDeserializingMethod: MyClass
.ctor: MyClass
OnDeserializedMethod: MyClass
< deserializing

Example code:

using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
[Serializable]
class MyClass : ISerializable
{
    public MyClass() { Trace(); }
    protected MyClass(SerializationInfo info, StreamingContext context) { Trace(); }
    public void GetObjectData(SerializationInfo info, StreamingContext context) { Trace(); }
    void Trace([CallerMemberName] string caller = null)
    {
        System.Console.WriteLine("{0}: {1}", caller, GetType().Name);
    }
    [OnDeserializing()]
    internal void OnDeserializingMethod(StreamingContext context) { Trace(); }

    [OnDeserialized()]
    internal void OnDeserializedMethod(StreamingContext context) { Trace(); }

    [OnSerializing()]
    internal void OnSerializingMethod(StreamingContext context) { Trace(); }

    [OnSerialized()]
    internal void OnSerializedMethod(StreamingContext context) { Trace(); }

    static void Main()
    {
        using (var ms = new MemoryStream())
        {
            var orig = new MyClass();
            var ser = new BinaryFormatter();
            System.Console.WriteLine("> serializing");
            ser.Serialize(ms, orig);
            System.Console.WriteLine("< serializing");
            ms.Position = 0;
            System.Console.WriteLine("> deserializing");
            ser.Deserialize(ms);
            System.Console.WriteLine("< deserializing");
        }
    }
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • this code will call the object constuctor var ctor = obj.GetType().GetConstructor(.... I would used InvokeMember to call the method http://www.codeproject.com/Articles/19911/Dynamically-Invoke-A-Method-Given-Strings-with-Met – Bassam Alugili Aug 22 '13 at 11:07
  • Thanks, prefect answer, I will mark it as accepted after I have found out why in my scenario the ctor gets called last. Maybe because the object isn't the root of the serialized tree. – Gerhard Aug 22 '13 at 11:07
  • OK Marc, your answer is correct only for the root object. See my answer below for a more complex scenario. Any idea why it behaves like that? By the way: very cool [CallerMemberName] Attribute! I didn't know that before (but only .NET 4.5) – Gerhard Aug 22 '13 at 11:41
4

The order of the calls depend on wether the object is the root of the serialized tree or some member of an object, which is also serialized in the same object graph. I get the following output with the extended example provided by Marc Gravell:

SerRoot.ctor
SerMember.ctor
> serializing
SerRoot.OnSerializingMethod
GetObjectData
SerMember.OnSerializingMethod
SerMember.GetObjectData
SerRoot.OnSerializedMethod
SerMember.OnSerializedMethod
< serializing
> deserializing
SerRoot.OnDeserializingMethod
SerMember.OnDeserializingMethod
SerMember.OnDeserializedMethod
SerMember.ctor(info, context)
SerRoot.ctor(info, context)
SerRoot.OnDeserializedMethod
< deserializing

Notice that in deserialization SerMember.ctor is called after SerMember.OnDeserializedMethod! This is the code:

        static void Main(string[] args)
    {
        using (var ms = new MemoryStream())
        {
            var orig = new SerRoot();
            var ser = new BinaryFormatter();
            System.Console.WriteLine("> serializing");
            ser.Serialize(ms, orig);
            System.Console.WriteLine("< serializing");
            ms.Position = 0;
            System.Console.WriteLine("> deserializing");
            ser.Deserialize(ms);
            System.Console.WriteLine("< deserializing");
        }
    }
[Serializable]
class SerRoot : ISerializable
{
    public SerMember m;
    public SerRoot()
    {
        System.Console.WriteLine("SerRoot.ctor");
        m = new SerMember();
    }
    protected SerRoot(SerializationInfo info, StreamingContext context)
    {
        System.Console.WriteLine("SerRoot.ctor(info, context)");
        m = info.GetValue("m", typeof(SerMember)) as SerMember;
    }
    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        System.Console.WriteLine("GetObjectData");
        info.AddValue("m", m);
    }
    [OnDeserializing()]
    internal void OnDeserializingMethod(StreamingContext context) { System.Console.WriteLine("SerRoot.OnDeserializingMethod"); }

    [OnDeserialized()]
    internal void OnDeserializedMethod(StreamingContext context) { System.Console.WriteLine("SerRoot.OnDeserializedMethod"); }

    [OnSerializing()]
    internal void OnSerializingMethod(StreamingContext context) { System.Console.WriteLine("SerRoot.OnSerializingMethod"); }

    [OnSerialized()]
    internal void OnSerializedMethod(StreamingContext context) { System.Console.WriteLine("SerRoot.OnSerializedMethod"); }

}
[Serializable]
class SerMember : ISerializable
{
    string text;
    public SerMember() 
    { 
        System.Console.WriteLine("SerMember.ctor");
        text = "test";
    }
    protected SerMember(SerializationInfo info, StreamingContext context) 
    {
        System.Console.WriteLine("SerMember.ctor(info, context)");
        text = info.GetString("text");
    }
    public void GetObjectData(SerializationInfo info, StreamingContext context) 
    { 
        System.Console.WriteLine("SerMember.GetObjectData");
        info.AddValue("text", text);
    }
    [OnDeserializing()]
    internal void OnDeserializingMethod(StreamingContext context) { System.Console.WriteLine("SerMember.OnDeserializingMethod"); }

    [OnDeserialized()]
    internal void OnDeserializedMethod(StreamingContext context) { System.Console.WriteLine("SerMember.OnDeserializedMethod"); }

    [OnSerializing()]
    internal void OnSerializingMethod(StreamingContext context) { System.Console.WriteLine("SerMember.OnSerializingMethod"); }

    [OnSerialized()]
    internal void OnSerializedMethod(StreamingContext context) { System.Console.WriteLine("SerMember.OnSerializedMethod"); }

}
Gerhard
  • 1,342
  • 2
  • 12
  • 23
  • That is ... intriguing. I can see what you are seeing, and I'm tempted to conclude that it looks an awful lot like a bug... I agree the order looks out of whack, but: I can't see it getting changed. The good news is that if you implement `IDeserializationCallback`, that gets called at the correct time. – Marc Gravell Aug 22 '13 at 13:49
  • 1
    This 'bug' has been causing me a lot of grief. Good catch. @Marc: Using IDeserializationCallback is not always a solution. Some serializes don't support it because it's not portable (e.g. Json.NET, [see here](https://github.com/JamesNK/Newtonsoft.Json/pull/421)). Seems like there no workaround other than avoiding ISerializable objects beyond the root object in the graph. – Allon Guralnek Jan 13 '16 at 07:49
2

[OnDeserializing] Indicates a method to be called just before deserialization [OnDeserialized] Indicates a method to be called just after deserialization

An [OnDeserializing] method acts as a pseudoconstructor for deserialization, and it is useful for initializing fields excluded from serialization:

[OnDeserializing] and [OnDeserialized] Deserialization bypasses all your normal constructors as well as field initializers. This is of little consequence if every field partakes in serialization, but it can be problematic if some fields are excluded via [NonSerialized].

I took this text from Albahari book C# 5.0 in nutshell page 713 check it out online a lot of example and description about your problem.

Thanks

Bassam Alugili
  • 16,345
  • 7
  • 52
  • 70