在 VB.net 中将多种文件类型保存到一个 potobuf-net 化的文件中

Posted

技术标签:

【中文标题】在 VB.net 中将多种文件类型保存到一个 potobuf-net 化的文件中【英文标题】:Saving multiple file types into one potobuf-net'ized file in VB.net 【发布时间】:2011-09-27 10:39:25 【问题描述】:

我正在编写一个将“地图”文件保存到 HD 的程序,以便稍后打开它们并显示相同的数据。我的地图最初只保存了一种数据类型,一组我自己的自定义对象,其属性为:idlayerxy。你可以在这里看到我为此做的代码:

<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

基本上,我只是将大量这些类序列化到一个文件中。现在我发现我必须保存地图的新部分,这些部分不一定是相同的类类型。

是否可以将多种类型保存到同一个文件中,并且仍然可以简单地从中读取?这是我在文件中写入和读取的代码:

    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

我必须在保存文件中添加 3 种或更多类类型,我不希望将其拆分,因为这样会让我的客户感到困惑。

基本上,我必须添加类似于以下内容的内容:

具有 XYValue 的类 具有 NameValue 的类 具有 NameENUMVALUEXY 的类, INTEGERVALUE,以及其他一些东西(这个必须包含相当多的数据)。

我使用的是 VB.net,所以所有 .net 答案都是可以接受的。谢谢!如果您需要任何说明,请在 cmets 中说明。

【问题讨论】:

【参考方案1】:

这里有 3 个选项:

第一种选择是编写一个包含 3 个容器的包装类:

 [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
 

并序列化它的一个实例;但是请注意,此处的订单将丢失 - 即在反序列化之后,Foo0, Bar0, Foo1Foo0, Foo1, Bar0 之间没有区别 - 要么将导致 SomeNameFoo0Foo1,以及 AnotherName 与 @ 987654328@。此选项与您现有的数据兼容,因为在内部序列化“Foo 列表”与“将 Foo 列表作为字段 1 的包装类”之间没有区别。

第二个选项是模仿上述方法,但在反序列化期间进行手动类型检查 - 这涉及使用非通用 API 并提供将字段编号 (1,2,3) 映射到类型 (@987654329) 的委托@,Bar,Blap)。这对于非常大的流或有选择地从流中提取对象 很有用,因为它允许您单独处理单个对象(或忽略它们)。使用这种方法,您还可以构建 累积文件,而不是构建整个列表。不过,这个例子有点复杂,所以我宁愿不添加一个,除非它感兴趣。这种方法与您现有的数据兼容。

第三种方式是继承,即

[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*/ 

然后序列化一个List&lt;SomeBaseType&gt;,它恰好包含实例是Foo/Bar/Blap。这简单方便,并且很好地保持了秩序;但它完全与仅作为List&lt;Foo&gt; 序列化的数据不兼容 - 如果现有的序列化数据是一个问题,您将需要在格式之间迁移。

【讨论】:

@OP 你又救了我 Gravell 先生!我刚刚实现了您提供的 Wrapper 方法,没有任何问题!非常感谢!【参考方案2】:

在这种情况下,我发现“Serializer.Serialize”非常不干净,所以我将如何进行: 我会一次一个地手动编写变量!

例如,这是我将如何编写和读取 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

所以如果你想写很多那些 StrippedElement,只需写下元素的数量(int32,4bytes)就可以知道下次要从文件中读取多少。

【讨论】:

除非你确切地知道你在做什么,并且需要它绝对严格,否则序列化是,IMO,最好留给获胜的工具不要犯烦人的细微错误(例如,声明为本地方法的“读取器”(BinaryReader)可能很容易过度消耗缓冲区中的流...... BOOM!)。它不是大多数人应该关心的代码。另外:考虑“字节顺序”(可能在单声道上运行)。最后,为什么在最后一个例子中SE(已知是一个类)声明ByRef 因为我需要在 Sub 之外的 SE,它是一个全局变量.. Dim SE as StrippedElement'It is Empty ReadStrippedElement(MyFileStream,SE) Dim x as integer = SE.X 'Now SE包含数据 这不会改变任何东西......它是一个类;一旦你设置了SE.X,你就更新了对象。如果您将参数值重新分配到一个新实例,并且需要 assignment 对调用者可见 - 即 SE = new StrippedElement();(或VB等价物)。您的代码没有分配(Function 可能会更容易) 是的,一个函数可能更简单,但我通常以这种方式编码,并使函数返回一个布尔值(错误处理)

以上是关于在 VB.net 中将多种文件类型保存到一个 potobuf-net 化的文件中的主要内容,如果未能解决你的问题,请参考以下文章

将变量保存到 vb.net 中的文本文件

在 vb.net 中将“字符串”转换为“SQlCommand”

如何使用 VB.net 将特定文本框的内容保存到文件

为文本保存设置正确的文件路径 - vb.NET [重复]

vb.NET SaveAs 不保存所有 Excel 数据

vb.net 应用程序将 xml 转换为文本