Search code examples
.netvb.netserializationdeserializationbinaryformatter

Objects having NonSerialized attribute are not created when deserializing


Using the BinaryFormatter class:

<Serializable>
Class Class1
    Public Property List1 as New List(Of Something)

    <NonSerialized> 
    Public Property List2 as New List(Of Something)
End Class

when I serialize an object like this one and then deserialize it, List2 will be set to Nothing.

What is the correct way to handle this?
I don't want List2 to be serialized, but I do want it to be an empty list when I deserialize the class.


Solution

  • BinaryFormatter doesn't call a class Constuctor, nor it initializes the class object in any way: the opposite, it creates the object non-initialized, in memory.
    You can see this in the .Net Source code: when the ParseObject() method is called, the class Object is generated by the GetUninitializedObject(Type type) method.
    The next call is made to a [MethodImpl] function, nativeGetUninitializedObject((RuntimeType)type), so we have to stop here, but it's already quite clear what happened: the returned Object represents a non-initialized type, thus none of the non-serialized fields have been initialized nor the class constructor has been called.

    If you don't actually need to use the BinaryFormatter class, Json.Net serializer/deserializer does initialize a class object instance when deserializing the type. It also respects the <NonSerialized> attribute. If you decide to use it, you don't need to modify the class object definition.

    If you must use a BinaryFormatter, you have two options:


    Implementing IDeserializationCallback:

    <NonSerialized> applies to fields: the List2 Auto property is changed into an Instance Field (the Property version is preserved in the next example):

    <Serializable>
    Class Class1
        Implements IDeserializationCallback
    
        <NonSerialized>
        Public List2 As List(Of Integer) = New List(Of Integer)()
    
        Public Property List1 As New List(Of String)
    
        Public Sub OnDeserialization(sender As Object) Implements IDeserializationCallback.OnDeserialization
            List2 = New List(Of Integer)
        End Sub
    End Class
    

    Using the <OnDeserializing> attribute:

    When the <OnDeserializing> attribute is added to a method, we don't need to implement the IDeserializationCallback Interface.

    Here, a new standard Property with a backing field is added to the Class type.
    The <NonSerialized> attribute is applied to List3's backing field:
    (as a note, the [field: NonSerialized] attribute has been added to Properties in c# 7.3)

    <Serializable>
    Class Class1
    
        <NonSerialized>
        Public List2 As List(Of Integer) = Nothing
    
        <NonSerialized>
        Private m_List3 As List(Of Double)
    
        Public Sub New()
            List2 = New List(Of Integer)
        End Sub
    
        Public Property List1 As New List(Of String)
    
        Public Property List3 As List(Of Double)
            Get
                Return m_List3
            End Get
            Set
                m_List3 = Value
            End Set
        End Property
    
        <OnDeserializing()>
        Friend Sub OnDeserialization(ByVal context As StreamingContext)
            List2 = New List(Of Integer)()
            m_List3 = New List(Of Double)()
        End Sub
    End Class
    

    In both cases, the BinaryFormatter's Deserialize() method will recreate the serialized class object with the non-serialized Lists initialized but empty:

    Dim formatter = New BinaryFormatter()
    
    Dim cls1 = New Class1() With {
        .List1 = New List(Of String) From {"1", "2", "3"},
        .List2 = New List(Of Integer) From {4, 5, 6}
    }
    
    Using writer = New FileStream(Path.Combine(AppContext.BaseDirectory(), 
        "Class1Serialized.bin"), FileMode.Create, FileAccess.Write)
        formatter.Serialize(writer, cls1)
    End Using
    
    Dim cls1Deserialized As Class1 = Nothing
    Using reader = New FileStream(Path.Combine(AppContext.BaseDirectory(), 
        "Class1Serialized.bin"), FileMode.Open, FileAccess.Read)
        cls1Deserialized = TryCast(formatter.Deserialize(reader), Class1)
    End Using
    

    Using NewtonSoft.Json:

    Dim cls1 As New Class1() With {
        .List1 = New List(Of String) From {"1", "2", "3"},
        .List2 = New List(Of Integer) From {4, 5, 6}
    }
    
    ' Serialize
    Dim class1Json = JsonConvert.SerializeObject(cls1)
    ' [...]
    ' Deserialize
    Dim cls1b = JsonConvert.DeserializeObject(Of Class1)(class1Json)