-
Aug 26th, 2011, 09:46 AM
#1
Thread Starter
Member
Binary Serialization and Custom Collections
I used the ideas from this post to create a custom collection of my custom class, so that I would be able to easily sort my collection. My project uses binary serialization to save everything (well the important stuff anyway) to a file. The problem is that my custom collection now breaks the serialization.
I can't seem to figure out how to get the custom collection to serialize!
Here is my code for the custom collection class:
VB.NET Code:
Imports System.ComponentModel
Imports System.Runtime.Serialization
Imports System.Runtime.Serialization.Formatters.Binary
<Serializable()> _
Public Class FGSDataCollection
Inherits System.ComponentModel.BindingList(Of FGSData)
Implements ISerializable
#Region "Initializer"
Public Sub New()
' need this for new empty list
End Sub
#End Region 'Initializer
#Region "Types"
Private Class FGSDataComparer
Implements System.Collections.Generic.IComparer(Of FGSData)
Private prop As PropertyDescriptor
Private direction As ListSortDirection
Public Function Compare(ByVal x As FGSData, ByVal y As FGSData) As Integer Implements IComparer(Of FGSData).Compare
Dim result As Integer = DirectCast(Me.prop.GetValue(x), IComparable).CompareTo(Me.prop.GetValue(y))
If Me.direction = ListSortDirection.Descending Then
result = -result
End If
Return result
End Function
Public Sub New(ByVal prop As PropertyDescriptor, ByVal direction As ListSortDirection)
Me.prop = prop
Me.direction = direction
End Sub
End Class
#End Region 'Types
#Region "Variables"
Private _sort As String
Private _sortProperty As PropertyDescriptor
Private _sortDirection As ListSortDirection
#End Region 'Variables
#Region "Properties"
Public Property Sort() As String
Get
Return Me._sort
End Get
Set(ByVal value As String)
Dim prop As PropertyDescriptor = Nothing
Dim direction As ListSortDirection
Me.ParseSortClause(value, prop, direction)
Me._sort = value
If prop Is Nothing Then
Me.RemoveSortCore()
Else
Me.ApplySortCore(prop, direction)
End If
End Set
End Property
Protected Overrides ReadOnly Property SortDirectionCore() As System.ComponentModel.ListSortDirection
Get
Return Me._sortDirection
End Get
End Property
Protected Overrides ReadOnly Property SortPropertyCore() As System.ComponentModel.PropertyDescriptor
Get
Return Me._sortProperty
End Get
End Property
Protected Overrides ReadOnly Property SupportsSortingCore() As Boolean
Get
Return True
End Get
End Property
#End Region 'Properties
#Region "Methods"
Protected Overrides Sub ApplySortCore(ByVal prop As PropertyDescriptor, ByVal direction As ListSortDirection)
Me._sortProperty = prop
Me._sortDirection = direction
Dim upperBound As Integer = Me.Items.Count - 1
Dim items(upperBound) As FGSData
Me.Items.CopyTo(items, 0)
Array.Sort(items, New FGSDataComparer(prop, direction))
For index As Integer = 0 To upperBound
Me.Items(index) = items(index)
Next
Me.OnListChanged(New ListChangedEventArgs(ListChangedType.Reset, 0))
End Sub
Protected Overrides Sub RemoveSortCore()
Me._sortProperty = Nothing
Me._sortDirection = Nothing
End Sub
Private Sub ParseSortClause(ByVal clause As String, _
ByRef prop As PropertyDescriptor, _
ByRef direction As ListSortDirection)
If clause IsNot Nothing AndAlso clause.Trim() <> String.Empty Then
Dim parts As String() = clause.Split(" "c)
If parts.Length > 2 Then
Throw New ArgumentException("Invalid sort clause")
End If
For index As Integer = 0 To parts.GetUpperBound(0)
parts(index) = parts(index).Trim()
Next
prop = TypeDescriptor.GetProperties(GetType(FGSData))(parts(0))
If prop Is Nothing Then
Throw New ArgumentException("Invalid property name")
End If
If parts.Length = 1 OrElse _
parts(1) = String.Empty OrElse _
String.Compare(parts(1), "ASC", True) = 0 Then
direction = ListSortDirection.Ascending
ElseIf String.Compare(parts(1), "DESC", True) = 0 Then
direction = ListSortDirection.Descending
Else
Throw New ArgumentException("Invalid sort direction")
End If
End If
End Sub
#End Region 'Methods
#Region "Serialization"
Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
' This gets the data without error.
' The list contains the correct number of items,
' but each item is Nothing.
Dim data As List(Of FGSData) = info.GetValue("FGSData", GetType(List(Of FGSData)))
For Each fgs As FGSData In data
Me.Add(fgs)
Next
' I think I want to do this, but Me.Items is read only
'Me.Items = info.GetValue("FGSData", GetType(List(Of FGSData)))
' Ultimately, this is the part that is giving me problems.
End Sub
Public Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext) Implements ISerializable.GetObjectData
info.AddValue("FGSData", Me.Items)
End Sub
#End Region 'Serialization
End Class
-
Aug 26th, 2011, 09:53 AM
#2
Re: Binary Serialization and Custom Collections
If FGSData itself marked as serializable?
-
Aug 26th, 2011, 10:20 AM
#3
Thread Starter
Member
Re: Binary Serialization and Custom Collections
If FGSData itself marked as serializable?
Good question. Yes, it is serializable. Before I implemented the custom collection class, I was using a generic List(Of FGSData), and the serialization for FGSData was working fine.
Also, to be more specific, saving to a file appears to be working fine. I don't know how to actually check the binary file to be sure, but stepping through with the debugger shows the GetObjectData method of the FGSDataCollection class firing once, and then the GetObjectData method of the FGSData class firing for each item in the FGSDataCollection. This behavior makes sense to me. However, when I try to Deserialize (open) the file, only the GetObjectData method of the FGSDataCollection class fires. Additionally, when I try to use the debugger to view the contents in the deserialized collection a System.TypeLoadException is shown.
I'm confident that it has to do with this section of code:
VB.NET Code:
Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
' This gets the data without error.
' The list contains the correct number of items,
' but each item is Nothing.
Dim data As List(Of FGSData) = info.GetValue("FGSData", GetType(List(Of FGSData)))
For Each fgs As FGSData In data
Me.Add(fgs)
Next
' I want to do this, but Me.Items is read only
'Me.Items = info.GetValue("FGSData", GetType(List(Of FGSData)))
End Sub
Or possibly the deserialize method in the FGSData class, which is:
VB.NET Code:
Public Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
_UnitNumber = info.GetString("_UnitNumber")
_TagNumber = info.GetString("_TagNumber")
_ProjectNumber = info.GetString("_ProjectNumber")
_PIDNumber = info.GetString("_PIDNumber")
_Service = info.GetString("_Service")
_FilledBy = info.GetString("_FilledBy")
_FilledDate = info.GetDateTime("_FilledDate")
_UnitDescription = info.GetString("_UnitDescription")
_ProjectDescription = info.GetString("_ProjectDescription")
_EquipmentType = info.GetInt32("_EquipmentType")
_EquipmentOther = info.GetString("_EquipmentOther")
_MaterialType = info.GetUInt64("_MaterialType")
_MaterialOther = info.GetString("_MaterialOther")
_Temperature = info.GetUInt16("_Temperature")
_Pressure = info.GetUInt16("_Pressure")
_Phase = info.GetUInt16("_Phase")
_GasWeight = info.GetUInt16("_GasWeight")
_H2SConcentration = info.GetUInt16("_H2SConcentration")
_Occupancy = info.GetUInt16("_Occupancy")
_IgnitionSources = info.GetUInt16("_IgnitionSources")
_IgnitionOther = info.GetString("_IgnitionOther")
_ProcessEnvironment = info.GetUInt16("_ProcessEnvironment")
_ProcessOther = info.GetString("_ProcessOther")
_Congestion = info.GetUInt16("_Congestion")
_IPLCredit = info.GetUInt16("_IPLCredit")
_Notes = info.GetString("_Notes")
End Sub
I'm wondering if I need to call New() in the above method?
-
Aug 26th, 2011, 10:44 AM
#4
Re: Binary Serialization and Custom Collections
That is the new method above. Unless you are changing the format of objects between versions, you should not have to do any custom serialization at all. You can remove items easy enough, and add new ones with an attribute to allow you not to fail (er ... OptionalField?), so to replace a collection you could just remove the old name, create a new one of the correct type and save the custom handlers.
-
Aug 26th, 2011, 12:48 PM
#5
Thread Starter
Member
Re: Binary Serialization and Custom Collections
Yes, forget I mentioned calling the New() method. However, I don't understand what you're trying to explain. The tutorials I've read about using binary serialization of custom classes required implementing ISerializable, which in turn requires:
VB.NET Code:
Public Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
End Sub
Public Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext) Implements ISerializable.GetObjectData
End Sub
Assuming the following imports:
VB.NET Code:
Imports System.Runtime.Serialization
Imports System.Runtime.Serialization.Formatters.Binary
have been imported.
-
Aug 26th, 2011, 02:09 PM
#6
Re: Binary Serialization and Custom Collections
Not at all, your mark as class as serializable, and then you serialize it . .Net works out all the types. The only time to get involved in customization (that I have found) is when you change a type and need to convert it differently, or when you have deleted 2 variables to combine into 1, ie 2 booleans into a single enum.
A quick search came up with this, I'll extract the bits that make sense:
Code:
<Serializable()> Public Class MyTestClass
Public Num1 As Integer
Public Num2 As Integer
End Class
Dim ser As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
Dim myObject As New MyTestClass With {Num1 = 12, Num2 = 23}
Using fs As New IO.StreamWriter("c:\temp\myfilename.xml")
ser.Serialize(fs, myObject)
End Using
Using fs As New IO.StreamReader("c:\temp\myfilename.xml")
Dim myObjectCopy As MyTestClass = DirectCast(ser.Deserialize(fs), MyTestClass)
End Using
*note, just copied and edited, should work but hopefully you get the picture.
Last edited by Grimfort; Aug 26th, 2011 at 02:14 PM.
-
Aug 26th, 2011, 02:24 PM
#7
Re: Binary Serialization and Custom Collections
one thing I've had issue with in the past regarding serializatoin is read-only properties. I could get them to serialize, but not deserialize (because the class didn't have a set method for the property). Now this was using xml serialization, not binary, so I don't know for sure if that's an issue with binary serialization. I was able to work around it by using the XMLSerializeIgnore attribute markup (or something along those lines). I then just had to make sure to expose the data in an alternate format for (de)serialization.
-tg
-
Sep 6th, 2011, 10:41 AM
#8
Thread Starter
Member
Re: Binary Serialization and Custom Collections
Not at all, your mark as class as serializable, and then you serialize it . .Net works out all the types.
I have found that this only works for serializing the default types/classes. From my experience, if you want to serialize a custom type or class, you're forced to dive into implementing the ISerializable interface.
Additionally, I tried removing ISerializable from my custom collection class, and it still did not work.
Nobody else has had problems trying to serialize a custom collection?
-
Sep 6th, 2011, 10:58 AM
#9
Re: Binary Serialization and Custom Collections
No. I have used binary serialization in a few different scenarios with custom classes and lists of those classes (and for a class that had a list of custom classes that each had a list of custom classes). I've never had to do more than mark all the classes as Serializable(). I've done this serialization in a single project, as well as in dlls for passing serialized custom classes between programs using a UDP connection.
Frankly, it has always been so dead simple to implement that I really can't say why you are having this issue. When something always works the first and easiest way you try it, you tend not to learn all that much about it. All I can say is that you must be missing something. Perhaps you can describe what led you to conclude that you had to implement the ISerializable interface.
My usual boring signature: Nothing
-
Sep 6th, 2011, 11:01 AM
#10
Thread Starter
Member
Re: Binary Serialization and Custom Collections
Okay, I've finally figured out how to deserialize my custom collection. I don't know if this is how it's supposed to be done, but it works. Here is the code:
Code:
Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
' retrieve the count from the file
Dim iCnt As Integer = info.GetInt32("FGSDataCount")
' get each of the items from the file based on the count
For i As Integer = 0 To iCnt - 1
Me.Add(info.GetValue("FGSData" & i.ToString, GetType(FGSData)))
Next
End Sub
Public Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext) Implements ISerializable.GetObjectData
' store the count to the file
info.AddValue("FGSDataCount", Me.Count)
' iterate through each item and add it to the file
For i As Integer = 0 To Me.Count - 1
info.AddValue("FGSData" & i.ToString, Me.Item(i))
Next
End Sub
So, first I save the collection count to the file. Then, I iterate through the list of items in the collection, and add each one to the file, giving each a unique name (based on position in list). Then when I want to deserialize the file, I first retrieve the count from the file. Then, I loop from 0 to count and using the unique name, I add each item back to my collection.
-
Sep 6th, 2011, 11:03 AM
#11
Thread Starter
Member
Re: Binary Serialization and Custom Collections
Perhaps you can describe what led you to conclude that you had to implement the ISerializable interface.
I wish I could remember where I read that I needed to do that. Clearly, I don't need to, so I'll make a copy of my project and remove all of the ISerializable interfaces and see what happens...let you guys know.
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|