开发者

Saving multiple file types into one potobuf-net'ized file in VB.net

I'm writing a program that saves 'map' files to the HD so that I can open them later and display the same data. My maps originally saved only one data type, a set of my own custom objects with the properties: id, layer, x, and y. You can see the code I did for that, here:

<ProtoContract()> _
Public Class StrippedElement
    <ProtoMember(1)> _
    Public Property X() As Integer
        Get
            Return m_X
        End Get
        Set(ByVal value As Integer)
            m_X = value
        End Set
    End Property
    Private m_X As Integer
    <ProtoMember(2)> _
    Public Property Y() As Integer
        Get
            Return m_Y
        End Get
        Set(ByVal value As Integer)
            m_Y = value
        End Set
    End Property
    Private m_Y As Integer
    <ProtoMember(3)> _
    Public Property Id() As Long
        Get
            Return m_Id
        End Get
        Set(ByVal value As Long)
            m_Id = value
        End Set
    End Property
    Private m_Id As Long
    <ProtoMember(4)> _
    Public Property Layer() As String
        Get
            Return m_Layer
        End Get
        Set(ByVal value As String)
            m_Layer = value
        End Set
    End Property
    Private m_Layer As String
End Class

Basically, I'm just serializing tons and tons of these classes into one file. Now I've come to find out that I have to save new parts of the map that aren't necessarily the same class type.

Is it possible to save multiple types to the same file and still read from it just as simply? Here is my code to write and read to/from the file:

    Public Shared Sub Save(ByVal File As String, ByVal Map As RedSimEngine.Map)
        Dim nPBL As New List(Of StrippedElement)
        For z As Integer = 0 To Grid.LAYERLIMIT
            For x As Integer = 0 To Grid.GRIDLIMIT
                For y As Integer = 0 To Grid.GRIDLIMIT
                    Dim currentCell As GridElement = Map.Level.getCell(z, x, y)
                    If currentCell IsNot Nothing Then
                        If currentCell.Archivable Then
                            Dim nStEl As New StrippedElement
                            nStEl.Id = currentCell.getId()
                            nStEl.Layer = currentCell.getLayer()
                            nStEl.X = currentCell.X
                            nStEl.Y = currentCell.Y
                            nPBL.Add(nStEl)
                        End If
                    End If
                Next
            Next
        Next
        Serializer.Serialize(New FileStream(File, FileMode.Create), nPBL)
    End Sub

    Public Shared Function Load(ByVal File As String) As RedSimEngine.Map
        Dim nMap As New Map
        Dim nListOfSE As List(Of StrippedElement) = Serializer.Deserialize(Of List(Of StrippedElement))(New FileStream(File, FileMode.Open))
        For Each elm As StrippedElement In nListOfSE
            Dim nElm As GridElement = GridElement.createElementByIdAndLayer(elm.Layer, elm.Id)
            nElm.X = elm.X
            nElm.Y = elm.Y
            nMap.Level.setCell(nElm)
        Next
        Return nMap
    End Function

I have to add 3 or more class types to the save file, I'd rather not have it split up because then it would get confusing for my clients.

Basically, I have to add things similar to the following:

  • A class with X, Y, and Value
  • A class开发者_JS百科 with Name and Value
  • A class with Name, ENUMVALUE, X, Y, INTEGERVALUE, and a couple other things (This one will have to contain quite a bit of data).

I'm using VB.net so all .net answers are acceptable. Thanks! If you need any clarification, just say so in the comments.


You have 3 options here:

The first option is to write a wrapper class with 3 containers:

 [ProtoContract] public class MyData {
     [ProtoMember(1)] public List<Foo> SomeName {get;set;} // x,y,value
     [ProtoMember(2)] public List<Bar> AnotherName {get;set;} // name,value
     [ProtoMember(3)] public List<Blap> ThirdName {get;set;} // etc
 }

and serialize an instance of that; note, however, that the order will be lost here - i.e. after deserializing there is no difference between Foo0, Bar0, Foo1 and Foo0, Foo1, Bar0 - either will result in SomeName with Foo0 and Foo1, and AnotherName with Bar0. This option is compatible with your existing data, as internally there is no difference between serializing "a list of Foo" vs "a wrapper class with a list of Foo as field 1".

The second option is to mimic the above, but with a manual type-check during deserialization -this involves using the non-generic API and providing a delegate that maps field numbers (1,2,3) to types (Foo,Bar,Blap). This is useful for very large streams, or selectively plucking objects from the stream, as it allows you to process individual objects (or ignore them) individually. With this approach you can also build the file cumulatively rather than building an entire list. The example is a bit more complex, though, so I'd rather not add one unless it is of interest. This approach is compatible with your existing data.

The third approach is inheritance, i.e.

[ProtoContract, ProtoInclude(1, typeof(Foo))]
[ProtoInclude(2, typeof(Bar)), ProtoInclude(3, typeof(Blap))]
public class SomeBaseType {}

[ProtoContract] public class Foo : SomeBaseType { /* properties etc*/ }
[ProtoContract] public class Bar: SomeBaseType { /* properties etc*/ }
[ProtoContract] public class Blap: SomeBaseType { /* properties etc*/ }

then serialize a List<SomeBaseType> which happens to contain instances that are Foo / Bar / Blap. This is simple and convenient, and preserves order nicely; but it is not quite compatible with data serialized simply as a List<Foo> - if existing serialized data is an issue you will need to migrate between the formats.


i find "Serializer.Serialize" very unclean in this kind of cases, so here is how i would proceed : i would write variables manually one at time!

for example, here is how i would write and read a StrippedElement :

    Sub WriteStrippedElement(ByVal Stream As IO.Stream, ByVal SE As StrippedElement)
    Stream.Write(BitConverter.GetBytes(SE.X), 0, 4) 'Write X:integer (4 bytes)
    Stream.Write(BitConverter.GetBytes(SE.Y), 0, 4) 'Write Y:integer (4 bytes)
    Stream.Write(BitConverter.GetBytes(SE.Id), 0, 8) 'Write Id:Long (8 bytes)
    Dim LayerBuffer() As Byte = System.Text.Encoding.Default.GetBytes(SE.Layer) 'Converting String To Bytes
    Stream.Write(BitConverter.GetBytes(LayerBuffer.Length), 0, 4) 'Write The length of layer, since it can't have a fixed size:integer (4 bytes)
    Stream.Write(LayerBuffer, 0, LayerBuffer.Length) 'Write The Layer Data
    Stream.Flush() 'We're Done :)
End Sub
Sub ReadStrippedElement(ByVal Stream As IO.Stream, ByRef SE As StrippedElement)
    Dim BinRead As New IO.BinaryReader(Stream) 'Making reading Easier, We can also use a BinaryWriter in the WriteStrippedElement For example
    SE.X = BinRead.ReadInt32 'Read 4 Bytes
    SE.Y = BinRead.ReadInt32 'Read 4 Bytes
    SE.Id = BinRead.ReadInt64 'Read 8 Bytes
    Dim Length As Integer = BinRead.ReadInt32 'Read 4 Bytes, the length of Layer
    Dim LayerBuffer() As Byte = BinRead.ReadBytes(Length) 'Read Layer Bytes
    SE.Layer = System.Text.Encoding.Default.GetString(LayerBuffer) 'Convert Back To String, and done.
End Sub

So if you like to write alot of those StrippedElement, just write down the number of elements (int32, 4bytes) to know how much to read from the file next time.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜