Search code examples
.netvb.netserializationbinaryformatter

How can I deserialize a MemoryStream to an array of Structs


I have an array of Structure (Person) which I serialized and formatted as follows

  <Serializable()> Structure Person
        Public strID As String
        Public strName As String
        Public strReport As String
        Public strAttend As String

    Public Shared Widening Operator CType(v As Person) As IO.MemoryStream
        Try
            Throw New NotImplementedException()
        Catch ex As Exception
            MsgBox("Failed to deserialise." + Chr(13) + "Reason: " & ex.Message)
        End Try
    End Operator
End Structure

Public Student(35) As Person
Dim bf As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter()
Dim ms as New System.IO.MemorySteam()

bf.Serialize(ms,Student(count))
My.Computer.FileSystem.WriteAllBytes(strFile1,ms.GetBuffer(),True)

The file is created and populated as desired. When I check it with WordPad all records are present. When I deserialize it, as below, I am only seeing the first record repeated. I am thinking either the pointer is not moving or I am going back to record 1 on each iteration. What am I missing?

Public Student(35) As Person
Dim bf As New    System.Runtime.Serialization.Formatters.Binary.BinaryFormatter()
Dim ms as New System.IO.MemorySteam()
Dim bytes As Byte() = My.Computer.FileSystem.ReadAllBytes(strFile1)
My.Computer.FileSystem.ReadAllBytes(strFile1)
Student(35) = DirectCast(bf.Deserialize(New MemoryStream(bytes)),Person)
ms.Seek(0,SeekOrigin.Begin)

For i = 0 to 19
    Student(i) = DirectCast(bf.Deserialize(New MemoryStream(bytes)),Person)
Next

Thank you, in advance, for any help or suggestions you may offer.


Solution

  • There is a fair amount wrong with the way are you going about it. Fundamentally, you can and should serialize and deserialize the entire collection at once. You cant step thru the memorystream item by item because you dont (cant) know the serialized size of each record. But there is more...

    Use a Class not a Structure

    MSDN has a good article one when and why to use a Class rather than a Structure. See Choosing Between Class and Struct

    Use a List instead of an array

    Arrays are grody and hard to work with because you need to now the size needed. Especially with hard coded magic numbers, if the number of students shrinks (or grows), you would not want to have to rewrite the app to change 35 everywhere.

    A List(Of T) grows as needed.

    Do not use GetBuffer

    The internal buffer used by a MemoryStream grows by itself as needed. But it does so by doubling the buffer size each time. Which means almost half the buffer could be unused space. Use .ToArray() to get the used portion. See MemoryStream.GetBuffer Method - read the Remarks section.

    But you do not even need a MemoryStream...

    Use a FileStream

    Rather than write to a memstream only to write it a file, you can open a filestream and write (or read) directly to that:

    My Class:

    <Serializable()>
    Public Class Student
        Public Property Name As String
        Public Property Gender As String
    
        Public Property EnrollDate As Date
        Public Property FavoriteColor As String
    
        Public Sub New()
    
        End Sub
        Public Sub New(n As String)
            Name = n
        End Sub
    
        Public Overrides Function ToString() As String
            Return Name & "     " & EnrollDate
        End Function
    End Class
    

    The ToString() override is to facilitate debugging/demo. Create a collection of Student object in a List(Of T):

    Dim Students As New List(Of Student)()
    Dim s As Student
    
    s = New Student("Ziggy F")
    s.EnrollDate = #5/17/2007#
    s.Gender = "M"
    s.FavoriteColor = "Orange"
    Students.Add(s)
    ... etc
    
    Console.WriteLine("BEFORE")
    For Each s In Students
        Console.WriteLine(s)
    Next
    

    Serialize:

    Dim filename As String = "C:\Temp\myStudents.dat"
    
    Using fs As New FileStream(filename, FileMode.Create)
        Dim bf As New BinaryFormatter
        bf.Serialize(fs, Students)
    End Using
    

    Deserialize and test:

    Dim newStudents As List(Of Student)
    ' to do check if exists
    Using fs As New FileStream(filename, FileMode.Open)
        Dim bf As New BinaryFormatter
        newStudents = DirectCast(bf.Deserialize(fs), List(Of Student))
    End Using
    
    Console.WriteLine("AFTER")
    For Each s In newStudents
        Console.WriteLine(s)
    Next
    

    All my students made the round trip:

    BEFORE
    Ziggy F 5/17/2007
    Zoey P 8/1/2007
    Hoover M 7/21/2005

    AFTER
    Ziggy F 5/17/2007
    Zoey P 8/1/2007
    Hoover M 7/21/2005

    See also: Beginner's Guide to Classes and Lists