Custom Dictionary class not serializing with correct file name

Viewed 61

To begin, I'm using vs 2019 community and targetting .net 4.7.2

I have created a custom dictionary class that I can instantiate with an extra property to hold the file name for serialization. I did the same thing for a custom list which works fine and I'm including for comparison purposes. The two classes are below..

<Serializable>
Public Class MyList(Of t)
    Inherits List(Of t)
    Public ReadOnly Property FileName As String

    Public Sub New(name As String)
        FileName = name
    End Sub
End Class

<Serializable>
Public Class MyDictionary(Of t, k)
    Inherits Dictionary(Of t, k)
    Public ReadOnly Property FileName As String

    Public Sub New(name As String)
        FileName = name
    End Sub

    Public Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
        MyBase.New(info, context)
    End Sub
End Class

The idea is that I can instantiate the List and the Dictionary multiple times with their own FileName property to make it more maintainable - the data is saved on multiple occasions in the rest of a large program and if I need to change the file names in the future, I only have to go to one place rather than many.

The instances are created as below ..

Friend Bookings As New MyDictionary(Of Integer, List(Of Booking))("Bookings")
Friend Vehicles As New MyList(Of Vehicle)("Vehicles")

When serializing the data, the code is as follows

SerializeDictionary(Bookings)
SerializeList(Vehicles)

There are more lists, but this serves as an example.

So with the list, the FileName property is used to create the file and everything works fine.

However with the dictionary, even though it is instantiated with a FileName property of "Bookings", when it comes to the serialization code, retrieving the property returns Nothing.

I have no idea why this happens.

Edit

As requested, here is he code for serializing and deserializing the lists and dictionaries

Friend Sub SerializeDictionary(Of k, t)(dc As MyDictionary(Of k, t))
    Dim filename As String = dc.FileName & ".bin"
    Dim fullPathName As String = Path.Combine(My.Settings.DataPath, filename)
    If dc.Count > 0 Then
        Using myFileStream As Stream = File.Create(fullPathName, 4096, FileOptions.WriteThrough)
            Dim serializer As New BinaryFormatter()
            serializer.Serialize(myFileStream, dc)
        End Using
    End If
End Sub

Friend Sub DeserializeDictionary(Of k, t)(ByRef dic As MyDictionary(Of k, t))

    Dim filename As String = dic.FileName & ".bin"
    Dim fileFullPathName As String = Path.Combine(My.Settings.DataPath, filename)
    If File.Exists(fileFullPathName) Then
        Using myFileStream As Stream = File.OpenRead(fileFullPathName)
            Dim deserializer As New BinaryFormatter()
            dic = CType(deserializer.Deserialize(myFileStream), MyDictionary(Of k, t))
        End Using
    End If
End Sub

Friend Sub SerializeList(Of t)(lst As MyList(Of t))
    Dim filename As String = lst.FileName & ".bin"
    Dim fullPathName As String = Path.Combine(My.Settings.DataPath, filename)
    If lst.Count > 0 Then
        Using myFileStream As Stream = File.Create(fullPathName)
            Dim serializer As New BinaryFormatter()
            serializer.Serialize(myFileStream, lst)
        End Using
    End If
End Sub

Friend Sub DeserializeList(Of t)(ByRef lst As MyList(Of t))
    Dim filename As String = lst.FileName & ".bin"
    Dim fileFullPathName As String = Path.Combine(My.Settings.DataPath, filename)
    If File.Exists(fileFullPathName) Then
        Using myFileStream As Stream = File.OpenRead(fileFullPathName)
            Dim deserializer As New BinaryFormatter()
            lst = CType(deserializer.Deserialize(myFileStream), MyList(Of t))
        End Using
    End If
End Sub
1 Answers

In the MyDictionary(Of T, K) class, override the GetObjectData method to store the FileName in the SerializationInfo, and retrieve it in the deserialization constructor which has the same signature.

Imports System.IO
Imports System.Runtime.Serialization
Imports System.Runtime.Serialization.Formatters.Binary

<Serializable>
Public Class MyDictionary(Of T, K)
    Inherits Dictionary(Of T, K)

    Public ReadOnly Property FileName As String

    Public Sub New(name As String)
        FileName = name
    End Sub

    Public Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
        MyBase.New(info, context)
        FileName = info.GetString("FileName")
    End Sub

    Public Overrides Sub GetObjectData(info As SerializationInfo, 
                                       context As StreamingContext)
        MyBase.GetObjectData(info, context)

        info.AddValue("FileName", FileName)
    End Sub
End Class

Also, you can create just a couple of methods to serialize and deserialize both types instead of creating a couple for each. The deserialize routine could be a function or a Shared function that returns the specified type so you don't need to pass the object ByRef as already mentioned above to just get the fileName from it. Just pass the fileName.

Imports System.Reflection

Public Sub SerializeType(Of T)(obj As T)
    'Instead of checking the types.
    'A shortcut since both classes has the FileName property...
    Dim fileFullName = Path.Combine(My.Settings.DataPath,
            String.Concat(GetType(T).GetProperty("FileName").
            GetValue(obj, BindingFlags.GetProperty, Nothing, Nothing, Nothing).ToString(),
            ".bin"))
    Dim bf As New BinaryFormatter
    Using fs As New FileStream(fileFullName, FileMode.Create)
        bf.Serialize(fs, obj)
    End Using
End Sub

Public Function DeserializeType(Of T)(fileName As String) As T
    Dim fileFullName = Path.Combine(My.Settings.DataPath, $"{fileName}.bin")

    If Not File.Exists(fileFullName) Then
        Return Nothing
    End If

    Dim bf As New BinaryFormatter

    Using fs As New FileStream(fileFullName, FileMode.Open)
        Return DirectCast(bf.Deserialize(fs), T)
    End Using
End Function

Implementing that, to serialize both Bookings and Vehicles objects:

SerializeType(Bookings)
SerializeType(Vehicles)

To Deserialize them:

Dim Vehicles = DeserializeType(Of MyList(Of Vehicle))("Vehicles")
Dim Bookings = DeserializeType(Of MyDictionary(Of Integer, List(Of Booking))("Bookings")
Related