Results 1 to 18 of 18

Thread: [RESOLVED] [2005] Problem Serializing

Hybrid View

  1. #1

    Thread Starter
    Frenzied Member
    Join Date
    Aug 2006
    Posts
    1,051

    Re: [2005] Problem Serializing

    Sure thing.

  2. #2

    Thread Starter
    Frenzied Member
    Join Date
    Aug 2006
    Posts
    1,051

    Re: [2005] Problem Serializing

    Solution To Problem:
    The problem was as follows:
    When you serialize an object (I'm using the BinaryFormatter) an object graph is created that includes any object pointed to by the primary object being serialized. If the primary object being serialized contains a delegate (as in maybe an event delegate) andalso another object has subscribed to receive an event notification from the primary object (the one being serialized) then the subscribing object is also included in the object graph. The runtime attempts to serialize the subscribing object. If the subscribing object is not serializable, an error occurs.

    The solution to this problem is to exclude the delegates from being serialized.
    To exclude the delegates from being serialized you must handle custom serialization.
    Custom serialization requires the class author to implement the ISerializable interface.
    Within the implementation of the ISerializable interface you must write code to exclude any class members of type Delegate or any type that derives from Delegate.

    The complete Person class that implements the ISerializable interface is shown below:

    Code:
    Imports System
    Imports System.Security.Permissions
    Imports System.Runtime.Serialization
    
    Namespace Utilities
    
        <Serializable()> _
        Public Class Person
            Implements ISerializable
    
    #Region "Event And Delegate Declarations"
    
            '<Serializable()> _
            Public Delegate Sub LastNameChangedEventHandler(ByVal sender As Object, ByVal e As LastNameChangedEventArgs)
            Public Event LastNameChanged As LastNameChangedEventHandler
    
            '<Serializable()> _
            Public Delegate Sub FirstNameChangedEventHandler(ByVal sender As Object, ByVal e As FirstNameChangedEventArgs)
            Public Event FirstNameChanged As FirstNameChangedEventHandler
    
    #End Region
    
    #Region "Private and Public Fields Not Wrapped By Properties (NONE)"
    
            'None
    
    #End Region
    
    #Region "Constructors"
    
            Sub New()
                Me.New("Unknown", "Unknown")
            End Sub
    
            Sub New(ByVal firstName As String, ByVal lastName As String)
    
                If (String.IsNullOrEmpty(firstName)) Then
                    Throw New ArgumentNullException("firstName")
                Else
                    mFirstName = firstName
                End If
    
                If (String.IsNullOrEmpty(lastName)) Then
                    Throw New ArgumentNullException("lastName")
                Else
                    mLastName = lastName
                End If
    
            End Sub
    
            'The CLR calls this special constructor when this (Me) object is deserialized
            'Should have Protected scope if other classes can be inherited from this class
            'Otherwise, if this class is sealed (cannot be inherited from) the scope can be Private
            Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
                SerializationHelpers.ISerializableContructorHelper(info, context, Me)
            End Sub
    
            <SecurityPermissionAttribute(SecurityAction.Demand, Flags:=SecurityPermissionFlag.SerializationFormatter)> _
            Protected Overridable Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext) _
              Implements ISerializable.GetObjectData
    
                SerializationHelpers.GetObjectDataHelper(info, context, Me)
    
            End Sub
    
    #End Region
    
    #Region "Instance Public Properties and Their Wrapped Fields"
    
            Private mLastName As String
            Public Property LastName() As String
                Get
                    Return mLastName
                End Get
                Set(ByVal value As String)
                    If Not mLastName = value Then
                        Dim Args As New LastNameChangedEventArgs(mLastName, value, Me)
                        Me.OnLastNameChanged(Args)
                        mLastName = value
                    End If
                End Set
            End Property
    
            Private mFirstName As String
            Public Property FirstName() As String
                Get
                    Return mFirstName
                End Get
                Set(ByVal value As String)
                    If Not mFirstName = value Then
                        Dim args As New FirstNameChangedEventArgs(mFirstName, value, Me)
                        Me.OnFirstNameChanged(args)
                        mFirstName = value
                    End If
                End Set
            End Property
    
    #End Region
    
    #Region "Instance Public Methods"
    
            Public Overrides Function ToString() As String
                Return String.Concat(FirstName, " ", LastName)
            End Function
    
            Protected Overridable Sub OnLastNameChanged(ByVal e As LastNameChangedEventArgs)
                RaiseEvent LastNameChanged(Me, e)
            End Sub
    
            Protected Overridable Sub OnFirstNameChanged(ByVal e As FirstNameChangedEventArgs)
                RaiseEvent FirstNameChanged(Me, e)
            End Sub
    
    #End Region
    
    #Region "Static Public Methods and Properties (NONE)"
    
            'NONE
    
    #End Region
    
    #Region "Methods In Interfaces (NONE)"
    
            'NONE
    
    #End Region
    
    #Region "Private Helper Methods (NONE)"
    
            'NONE
    
    #End Region
    
        End Class
    
    End Namespace

  3. #3

    Thread Starter
    Frenzied Member
    Join Date
    Aug 2006
    Posts
    1,051

    Re: [2005] Problem Serializing

    Opps clicked on Submit Reply instead of Preview Post...more in next post

  4. #4

    Thread Starter
    Frenzied Member
    Join Date
    Aug 2006
    Posts
    1,051

    Re: [2005] Problem Serializing

    You'll notice a Protected Sub New and a Protected Overridable Sub GetObjectData highlighted in bold text within the Person class listed two posts above.
    These are the two additional code blocks required by the ISerializable interface.

    You'll also notice that in those blocks reference is made to a helper class as found below:

    Credit for the code goes to the authors of Practical Guidelines And Best Practices For Microsoft Visual Basic and Visual C# Developers a book I would highly recommend to anyone.

    Code:
    Imports System.Runtime.Serialization
    Imports System.Reflection
    
    Namespace Utilities
    
        Public Class SerializationHelpers
    
            ''' <summary>
            ''' Helper routine for serializing objects.
            ''' (Place a call to this sub into the Serializable Classes Protected Overridable Sub GetObjectData)
            ''' i.e. place into: Protected Overridable Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext)
            ''' </summary>
            ''' <param name="info"></param>
            ''' <param name="context"></param>
            ''' <param name="obj"></param>
            ''' <remarks></remarks>
            Public Shared Sub GetObjectDataHelper(ByVal info As SerializationInfo, ByVal context As StreamingContext, ByVal obj As Object)
    
                'Get list of serializable members
                Dim members() As MemberInfo = GetFilteredSerializableMembers(obj.GetType)
    
                'Read the value of each member
                Dim values() As Object = FormatterServices.GetObjectData(obj, members)
    
                'Store in the SerializationInfo Object, using member name
                For i As Integer = 0 To members.Length - 1
                    info.AddValue(members(i).Name, values(i))
                Next
    
            End Sub
    
            ''' <summary>
            ''' Helper routine for deserializing objects.
            ''' Place a call to this function into the Serializable Constructor; 
            ''' i.e. place into:   Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext))
            ''' </summary>
            ''' <param name="info"></param>
            ''' <param name="context"></param>
            ''' <param name="obj"></param>
            ''' <remarks></remarks>
            Public Shared Sub ISerializableContructorHelper(ByVal info As SerializationInfo, ByVal context As StreamingContext, ByVal obj As Object)
    
                'Get the list of serializable members for this object 
                'excluding members that are of type delegate (filtered)
                'Note that members()(below) contains all types of members from the class passed into this sub via obj (byval obj as Object)
                Dim members() As MemberInfo = GetFilteredSerializableMembers(obj.GetType)
    
                Dim values(members.Length - 1) As Object
    
                'Read the value for each member
                For i As Integer = 0 To members.Length - 1
    
                    'retrieve the members from members() that are of type field
                    'and then get all field's values
                    Dim member As MemberInfo = members(i)
                    If member.MemberType = MemberTypes.Field Then
                        Dim membertype As Type = CType(member, FieldInfo).FieldType
                        values(i) = info.GetValue(member.Name, membertype)
                    End If
                Next
    
                'assign all serializable members in one operation
                FormatterServices.PopulateObjectMembers(obj, members, values)
    
            End Sub
    
            ''' <summary>
            ''' Return an array of serializable MemberInfo for a given type.
            ''' (Same as FormatterServices.GetSerializableMember, but FILTERS OUT delegate fields.)
            ''' </summary>
            ''' <param name="ty"></param>
            ''' <returns></returns>
            ''' <remarks></remarks>
            Private Shared Function GetFilteredSerializableMembers(ByVal ty As Type) As MemberInfo()
    
                Dim al As New ArrayList
    
                'Get the list of serializable members
                For Each mi As MemberInfo In FormatterServices.GetSerializableMembers(ty)
    
                    'Add the mi object to the arraylist if it is NOT:
                    '(a FieldInfo object) [that IS also (AndAlso)] (found in the inheritance hierarchy of the Delegate class)
                    If Not (TypeOf mi Is FieldInfo AndAlso GetType([Delegate]).IsAssignableFrom(DirectCast(mi, FieldInfo).FieldType)) Then
                        al.Add(mi)
                    End If
                Next
                Return DirectCast(al.ToArray(GetType(MemberInfo)), MemberInfo())
            End Function
    
        End Class
    
    End Namespace
    You'll have to read up on the ISerializable interface and the purpose of it's members because a complete explanation would be lengthy.
    Upon reading up on how these two code blocks work in general; i.e. the purpose for the special sub New and the GetObjectData method you'll quickly see the importance of these helper methods.

    A complete example including the using class, the simple Person class, and the Helper class follows In the next post (was not accepted to this post because of it's size):

  5. #5

    Thread Starter
    Frenzied Member
    Join Date
    Aug 2006
    Posts
    1,051

    Re: [2005] Problem Serializing

    Using Class example:

    Code:
    Imports System.Runtime.Serialization
    Imports System.Runtime.Serialization.Formatters.Binary
    Imports System.IO
    Imports DILU.Utilities
    
    Public Class Form1
    
    #Region "Serialize"
    
        Private Sub Serialize(ByVal fileName As String, ByVal person As Person)
    
            Dim fs As FileStream = New FileStream(fileName, FileMode.OpenOrCreate)
    
            Dim bf As New BinaryFormatter
            Try
                bf.Serialize(fs, person)
            Catch exception As SerializationException
                MessageBox.Show("Failed to serialize. Reason: " & exception.Message)
                Throw
            Finally
                fs.Close()
            End Try
    
        End Sub
    
    #End Region
    
    #Region "Deserialize"
    
        Private Function Deserialize(ByVal fileName As String) As Person
    
            Dim fs As FileStream = New FileStream(fileName, FileMode.Open)
    
            Dim p As New Person
            Try
    
                Dim formatter As New BinaryFormatter
                p = DirectCast(formatter.Deserialize(fs), Person)
    
            Catch exception As Runtime.Serialization.SerializationException
                Console.WriteLine("Failed to deserialize. Reason: " & exception.Message)
                Throw
            Finally
                fs.Close()
            End Try
    
            Return p
    
        End Function
    
    #End Region
    
    #Region "Button One Click"
    
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    
            Dim p As Person
            Try
                p = New Person(" ", " ")
            Catch ex As ArgumentNullException
                p = Nothing
                p = New Person
            End Try
    
            Me.TextBox1.Text = p.FirstName
            Me.TextBox2.Text = p.LastName
    
            'yes, I actually do have a need for these two events :)
            AddHandler p.LastNameChanged, AddressOf PersonsLastNameChanged
            AddHandler p.FirstNameChanged, AddressOf PersonsFirstNameChanged
    
            Serialize("C:\BinaryNoDelegateSerialization.txt", p)
            Dim np As Person = Deserialize("C:\BinaryNoDelegateSerialization.txt")
    
            Me.TextBox3.Text = np.FirstName
            Me.TextBox4.Text = np.LastName
    
            Application.DoEvents()
    
            Threading.Thread.Sleep(3000)
    
            p.LastName = "New Last Name"
            p.FirstName = "New First Name"
    
        End Sub
    
    #End Region
    
    #Region "Event Handlers For Names Changed"
    
        Private Sub PersonsLastNameChanged(ByVal sender As Object, ByVal e As LastNameChangedEventArgs)
            Me.TextBox2.Text = e.NewLastName
        End Sub
    
        Private Sub PersonsFirstNameChanged(ByVal sender As Object, ByVal e As FirstNameChangedEventArgs)
            Me.TextBox1.Text = e.NewFirstName
        End Sub
    
    #End Region
    
    End Class
    Person Class:

    Code:
    Imports System
    Imports System.Security.Permissions
    Imports System.Runtime.Serialization
    
    Namespace Utilities
    
        <Serializable()> _
        Public Class Person
            Implements ISerializable
    
    #Region "Event And Delegate Declarations"
    
            '<Serializable()> _
            Public Delegate Sub LastNameChangedEventHandler(ByVal sender As Object, ByVal e As LastNameChangedEventArgs)
            Public Event LastNameChanged As LastNameChangedEventHandler
    
            '<Serializable()> _
            Public Delegate Sub FirstNameChangedEventHandler(ByVal sender As Object, ByVal e As FirstNameChangedEventArgs)
            Public Event FirstNameChanged As FirstNameChangedEventHandler
    
    #End Region
    
    #Region "Private and Public Fields Not Wrapped By Properties (NONE)"
    
            'None
    
    #End Region
    
    #Region "Constructors"
    
            Sub New()
                Me.New("Unknown", "Unknown")
            End Sub
    
            Sub New(ByVal firstName As String, ByVal lastName As String)
    
                If (String.IsNullOrEmpty(firstName)) Then
                    Throw New ArgumentNullException("firstName")
                Else
                    mFirstName = firstName
                End If
    
                If (String.IsNullOrEmpty(lastName)) Then
                    Throw New ArgumentNullException("lastName")
                Else
                    mLastName = lastName
                End If
    
            End Sub
    
            'The CLR calls this special constructor when this (Me) object is deserialized
            'Should have Protected scope if other classes can be inherited from this class
            'Otherwise, if this class is sealed (cannot be inherited from) the scope can be Private
            Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
                SerializationHelpers.ISerializableContructorHelper(info, context, Me)
            End Sub
    
            <SecurityPermissionAttribute(SecurityAction.Demand, Flags:=SecurityPermissionFlag.SerializationFormatter)> _
            Protected Overridable Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext) _
              Implements ISerializable.GetObjectData
    
                SerializationHelpers.GetObjectDataHelper(info, context, Me)
    
            End Sub
    
    #End Region
    
    #Region "Instance Public Properties and Their Wrapped Fields"
    
            Private mLastName As String
            Public Property LastName() As String
                Get
                    Return mLastName
                End Get
                Set(ByVal value As String)
                    If Not mLastName = value Then
                        Dim Args As New LastNameChangedEventArgs(mLastName, value, Me)
                        Me.OnLastNameChanged(Args)
                        mLastName = value
                    End If
                End Set
            End Property
    
            Private mFirstName As String
            Public Property FirstName() As String
                Get
                    Return mFirstName
                End Get
                Set(ByVal value As String)
                    If Not mFirstName = value Then
                        Dim args As New FirstNameChangedEventArgs(mFirstName, value, Me)
                        Me.OnFirstNameChanged(args)
                        mFirstName = value
                    End If
                End Set
            End Property
    
    #End Region
    
    #Region "Instance Public Methods"
    
            Public Overrides Function ToString() As String
                Return String.Concat(FirstName, " ", LastName)
            End Function
    
            Protected Overridable Sub OnLastNameChanged(ByVal e As LastNameChangedEventArgs)
                RaiseEvent LastNameChanged(Me, e)
            End Sub
    
            Protected Overridable Sub OnFirstNameChanged(ByVal e As FirstNameChangedEventArgs)
                RaiseEvent FirstNameChanged(Me, e)
            End Sub
    
    #End Region
    
    #Region "Static Public Methods and Properties (NONE)"
    
            'NONE
    
    #End Region
    
    #Region "Methods In Interfaces (NONE)"
    
            'NONE
    
    #End Region
    
    #Region "Private Helper Methods (NONE)"
    
            'NONE
    
    #End Region
    
        End Class
    
    End Namespace
    First Name Changed Event Args Class:

    Code:
    Namespace Utilities
    
        <Serializable()> _
        Public Class FirstNameChangedEventArgs
            Inherits EventArgs
    
            Sub New(ByVal oldFirstName As String, ByVal newFirstName As String, ByVal sendingPerson As Person)
                mOldFirstName = oldFirstName
                mNewFirstName = newFirstName
            End Sub
    
            Private mOldFirstName As String
            Public ReadOnly Property OldFirstName() As String
                Get
                    Return mOldFirstName
                End Get
            End Property
    
            Private mNewFirstName As String
            Public ReadOnly Property NewFirstName() As String
                Get
                    Return mNewFirstName
                End Get
            End Property
    
            Private mPerson As Person
            Public ReadOnly Property Person() As Person
                Get
                    Return mPerson
                End Get
            End Property
    
        End Class
    
    End Namespace
    The last name changed event args class is similar to the above code

    The Serialization Helpers Class is posted again in the next post because it was not accepted here due to it's size.

  6. #6

    Thread Starter
    Frenzied Member
    Join Date
    Aug 2006
    Posts
    1,051

    Re: [2005] Problem Serializing

    The Serialization Helpers Class:

    Code:
    Imports System.Runtime.Serialization
    Imports System.Reflection
    
    Namespace Utilities
    
        Public Class SerializationHelpers
    
            ''' <summary>
            ''' Helper routine for serializing objects.
            ''' (Place a call to this sub into the Serializable Classes Protected Overridable Sub GetObjectData)
            ''' i.e. place into: Protected Overridable Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext)
            ''' </summary>
            ''' <param name="info"></param>
            ''' <param name="context"></param>
            ''' <param name="obj"></param>
            ''' <remarks></remarks>
            Public Shared Sub GetObjectDataHelper(ByVal info As SerializationInfo, ByVal context As StreamingContext, ByVal obj As Object)
    
                'Get list of serializable members
                Dim members() As MemberInfo = GetFilteredSerializableMembers(obj.GetType)
    
                'Read the value of each member
                Dim values() As Object = FormatterServices.GetObjectData(obj, members)
    
                'Store in the SerializationInfo Object, using member name
                For i As Integer = 0 To members.Length - 1
                    info.AddValue(members(i).Name, values(i))
                Next
    
            End Sub
    
            ''' <summary>
            ''' Helper routine for deserializing objects.
            ''' Place a call to this function into the Serializable Constructor; 
            ''' i.e. place into:   Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext))
            ''' </summary>
            ''' <param name="info"></param>
            ''' <param name="context"></param>
            ''' <param name="obj"></param>
            ''' <remarks></remarks>
            Public Shared Sub ISerializableContructorHelper(ByVal info As SerializationInfo, ByVal context As StreamingContext, ByVal obj As Object)
    
                'Get the list of serializable members for this object 
                'excluding members that are of type delegate (filtered)
                'Note that members()(below) contains all types of members from the class passed into this sub via obj (byval obj as Object)
                Dim members() As MemberInfo = GetFilteredSerializableMembers(obj.GetType)
    
                Dim values(members.Length - 1) As Object
    
                'Read the value for each member
                For i As Integer = 0 To members.Length - 1
    
                    'retrieve the members from members() that are of type field
                    'and then get all field's values
                    Dim member As MemberInfo = members(i)
                    If member.MemberType = MemberTypes.Field Then
                        Dim membertype As Type = CType(member, FieldInfo).FieldType
                        values(i) = info.GetValue(member.Name, membertype)
                    End If
                Next
    
                'assign all serializable members in one operation
                FormatterServices.PopulateObjectMembers(obj, members, values)
    
            End Sub
    
            ''' <summary>
            ''' Return an array of serializable MemberInfo for a given type.
            ''' (Same as FormatterServices.GetSerializableMember, but FILTERS OUT delegate fields.)
            ''' </summary>
            ''' <param name="ty"></param>
            ''' <returns></returns>
            ''' <remarks></remarks>
            Private Shared Function GetFilteredSerializableMembers(ByVal ty As Type) As MemberInfo()
    
                Dim al As New ArrayList
    
                'Get the list of serializable members
                For Each mi As MemberInfo In FormatterServices.GetSerializableMembers(ty)
    
                    'Add the mi object to the arraylist if it is NOT:
                    '(a FieldInfo object) [that IS also (AndAlso)] (found in the inheritance hierarchy of the Delegate class)
                    If Not (TypeOf mi Is FieldInfo AndAlso GetType([Delegate]).IsAssignableFrom(DirectCast(mi, FieldInfo).FieldType)) Then
                        al.Add(mi)
                    End If
                Next
                Return DirectCast(al.ToArray(GetType(MemberInfo)), MemberInfo())
            End Function
    
        End Class
    
    End Namespace

  7. #7

    Thread Starter
    Frenzied Member
    Join Date
    Aug 2006
    Posts
    1,051

    Re: [2005] Problem Serializing

    Due to the low number of replys to my original question in this thread, I assume that either the explanation was too lengthy or that not many people use custom serialization.

    Therefore, I decided to post a complete example others may find useful in the future.

    If anyone finds any errors in this code please let me know and I'll make corrections here for future viewers to see.

    Thanks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width