2010-10-21 66 views
1

所以我想知道如果在纯.NET中有一个答案来表示任意数据类型的集合。我知道那里有旧的VB6 Collections,但我一直在寻找类似于Generics的东西,但是不必在编译时指定类型,或者找到一种聪明的方法来让代码自行确定类型然后调用一些通用类。VB.NET中的任意类型的集合?

为什么?我很无聊,我认为尝试和实施我自己的NBT库,或NamedBinaryTag会很有趣。这是流行的Minecraft游戏中使用的存储格式。规范文档在这里:http://www.minecraft.net/docs/NBT.txt

我知道现有的实现,但如果我只是作为一个学习经验来更好地掌握文件流,字节数组,末尾形式,那么没有必要复制这些实现转换,和一般的.NET的东西(我曾经很多摆弄VB6/VBA,所以.NET是一个巨大的变化)。

挂我的是TAG_Compound。按照该规范,它基本上是任何其他Tag类型的对象的集合,包括附加嵌套的TAG_Compounds。你可以用这种格式做一些奇怪的嵌套/递归。

我已经在我的脑海中做了一个粗略的概述,如何做其他类,但任意类型的存储只是让我画一个空白的如何将它存储在存根类(clsTagCompound)因此,一个泛型类(clsNBT(Of T))可以使用泛型函数来访问有效负载。

List(Of T)看起来像它可以工作,如果我可以喂它一个共同的接口。但是由于Generic类将是主要组件,它的接口也是通用的,并且只会导致讨厌的泛型链(List(Of(clsNBT(Of XXX)))。因为这个规范适用于字节流,这里是一个非压缩NBT文件的样子(使用Minecraft编辑器创建的)的十六进制输出,它是一个包裹在TAG_Compound中的TAG_String,虽然没有具体说明,但它是通常第一个标记在NBT文件中找到,它封装了所有其他标签。

0A 00 04 72 6F 6F 74 08 00 06 66 6F 6F 62 61 72 00 07 50 49 52 41 54 45 21 00

从左到右:
字节1:TagType - 指定TAG_Compound。
字节2-3:TAG_Compound名称的字符串长度。
字节4-7:“root”,TAG_Compound的名称。
字节8:TagType - 指定TAG_String(嵌入在TAG_Compound中)。
字节9-10:TAG_String名称的字符串长度。
字节11-16:“foobar”,TAG_String的名称。
字节17-18:有效负载长度(TAG_String,所以字符串长度)。
字节19-25:“PIRATES!”,TAG_String的有效负载。
字节26:TagType - 指定TAG_End,标记TAG_Compound或TAG_List的结尾。

相同的基本原则适用于其他标签类型。非常简单的设计,但看起来非常强大。可能是为什么一个alpha级别的代码运行得很好的原因之一,特别是在Java中。

编辑:这是链接到级别规范。它给看到这些标签是如何协同工作的更容易理解的方式:
http://www.minecraftwiki.net/wiki/Alpha_Level_Format#level.dat_Format

注:我没有做任何MODS的游戏太有兴趣 - 我只是找到了NBT格式整齐,很简单,可以可能有用。已经在思考如何扩展格式以处理标记中的无符号类型(即TAG_UInteger),并且可能在幻数前加上未压缩的流(如Linux/Unix可执行文件在前四个字节中有“ELF”)。这将防止这些工具中的任何问题被用来打开任意/意外的数据格式(我也可能会将这些想法传递给游戏开发人员)。

编辑2:所以我改变了事情。 clsNamedBinaryTag现在是一个抽象类,它实现在通用接口中定义的通用方法:

Friend Interface INbt(Of T) 
    ... 
    Function GetPayload() As T 
    Function SetPayload(ByRef data As BinaryReader) As Boolean 
End Interface 


Friend MustInherit Class clsNamedBinaryTag(Of T) 
    Implements INbt(Of T) 

    ... 

    Protected Friend MustOverride _ 
    Function GetPayload() As T _ 
     Implements INbt(Of T).GetPayload 

    Protected Friend MustOverride _ 
    Function SetPayload(ByRef data As BinaryReader) As Boolean _ 
     Implements INbt(Of T).SetPayload 
End Class 

GetPayload是通用的方法,因为它会提取和返回任意类型的有效载荷。非常适合Strings等简单的事情。当我们遇到TAG_Compound时不是那么好。

我在做的事是让所有派生类实现INbt(Of T)。对于clsTagCompound,它的SetPayload方法将在解析化合物的名称字段后开始漫步字节流。对于它遇到的每个新的TagType,它理论上都会调用DirectCast上的Dim变为INbt(Of T)的临时变量,将其转换为定义该特定TagType的类。

但这似乎并没有按计划运作。我相信我的Catch 22甚至使用clsTagCompound,我仍然需要定义T,这就是我再次卡住的地方。我需要创建一个不通用的接口,但可以应用于各种标签类型的所有类,并仍然调用它们的GetPayload函数来返回特定标签的有效载荷。

回答

3

如果要使用可容纳任何内容的集合类型,可以使用非泛型集合(在System.Collections namespace中)或通用集合(Of Object)

如果要在运行时确定对象的类型,可以使用GetType method(以确切类型匹配)或Typeof [something] Is [sometype]构造来匹配某个接口/类的implements/inherits /。 More explanations on MSDN.

对于你的问题,我会写一个通用的接口ITagElement,将由类实现诸如TagCompoundTagStringTagList等,至于该TagList类,我会让它继承的一个类中System.Collections.ObjectModel namespace

+0

GetType我以前玩过。类型...对我来说是一个相当新的东西。似乎根据MSDN链接找出通用对象中包含的数据类型。但是不是很贵?如果我正确理解术语(并且在发现/使用ILDASM后),则需要对包含的数据类型进行装箱和拆箱,这需要几个周期。 – Kumba 2010-10-22 00:30:02

+0

我正在按标签类,顺便说一句。所以现在,我有clsTagString和clsTagCompound。 NBT规范提供了一个由一个TAG_Compound“root”和一个TAG_String“hello world”组成的小型测试用例NBT文件的URL,其值为“Bananrama”。我的目标是能够首先解析,然后从那里开始工作。 clsTagString是完整的,理论上应该可以工作。这是clsTagCompound,这被证明是一个愚蠢的,因为它作为其有效载荷任何其他标签,包括额外的TAG_compounds。泛型也许是答案,但是这些东西很难考虑。 – Kumba 2010-10-22 00:33:49

+0

进一步的(愚蠢的评论限制),我有一个基类,clsNamedBinaryTag,它包含所有标签通用的TagType,TagName和TagNameLength字段的常见访问器属性/函数。标签也可以没有名称 - 可以通过查看TagNameLength是否为0来确定。 问题在于Payload。每个标签实现不同的有效负载,所以TAG_String存储一个以其字符串长度为前缀的字符串。 TAG_Integer存储一个32位有符号整数值。 TAG_List存储(基本上)一个单一类型的数组。 – Kumba 2010-10-22 00:37:31

0

您可以使用对象或varientType类型作为存储任意一组对象的列表。

要在运行时找出对象类型,您可以使用反射。我从来没有在vb.net中使用它,但它是受支持的。

+0

是的,我想避免使用oldstyle VB6对象/集合。这招致了迟来的约束性处罚。反思是我已经读过的东西,虽然它比老派对象更快,但它仍然需要罚款,而不是使用泛型,或者找到理智的原因来强制输入代码。 – Kumba 2010-10-22 00:18:05

0

我不知道你正在谈论的这个结构的细节(你的链接到NBT.txt是无效的......),但据我所知你可能想要一个clsTagCompound类是你的基类。如果你有不同的含义TAG_Compound的不同变体,你可以声明那些作为从clsTagCompound

继承类然后你可以声明clsNBT作为一个集合类的clsTagCompound的(或其他 - 我并没有完全得到你的目的与clsNBT)。

要处理子的水平,这是最好的对在 clsTagCompound

Class clsTagCompound 

Public Name As String 
Private mChildren As New List(Of clsTagCompound) 
Public ReadOnly Property Children As List(Of clsTagCompound) 
    Get  
    Return mChildren 
    End Get 
End Property 
End Class 

Class clsTag1 
Inherits clsTagCompound 

End Class 

Class clsTag2 
Inherits clsTagCompound 

End Class 

对象儿童属性可以是对象从继承clsTagCompound任何类的混合。

+0

过去几天,Minecraft网站显然遭受了SYN Flood攻击。我想有人不能接受,它只是一个游戏...尝试每隔几小时重新加载页面。 – Kumba 2010-10-22 00:20:36

0

当处理固定格式的数据包时,我使用类似这样的东西。这也说明了解决“永恒性”的简单方法。我解码了前几个字节(使用你提供的示例),这样你就可以得到一个想法。

Enum NBT 'define the offsets into the packet, change names/add others as needed 
    byte1 = 0 
    byte23 = 1 
    byte47 = 3 
    byte8 = 7 
    'etc 
End Enum 

Dim swEndian As Boolean = True 

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click 
    Dim testData() As Byte = New Byte() {&HA, &H0, &H4, &H72, &H6F, &H6F, &H74, &H8, &H0, &H6, &H66, &H6F, &H6F, &H62, &H61, &H72, &H0, &H7, &H50, &H49, &H52, &H41, &H54, &H45, &H21, &H0} 

    Dim byte1 As Byte = testData(NBT.byte1) 

    Dim byte2 As Int16 = BitConverter.ToInt16(testData, NBT.byte23) 
    If swEndian Then byte2 = System.Net.IPAddress.NetworkToHostOrder(byte2) 

    Dim byte4 As Int32 = BitConverter.ToInt32(testData, NBT.byte47) 
    If swEndian Then byte4 = System.Net.IPAddress.NetworkToHostOrder(byte4) 

End Sub 
+0

我实际上发现,如果字节被加载到一个字节数组中,调用Array.Reverse()会将它们翻转成适当的BE格式。这更容易被证明。如果MS曾经决定修复BinaryReader/BinaryWriter以允许指定endianess,我可以在以后更改它。 – Kumba 2010-10-22 00:16:44

+0

如果你的例子中包含字符串和数字,该怎么办?反转数组也会反转字符串。 – dbasnett 2010-10-22 09:52:16