所以我想知道如果在纯.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
函数来返回特定标签的有效载荷。
GetType我以前玩过。类型...对我来说是一个相当新的东西。似乎根据MSDN链接找出通用对象中包含的数据类型。但是不是很贵?如果我正确理解术语(并且在发现/使用ILDASM后),则需要对包含的数据类型进行装箱和拆箱,这需要几个周期。 – Kumba 2010-10-22 00:30:02
我正在按标签类,顺便说一句。所以现在,我有clsTagString和clsTagCompound。 NBT规范提供了一个由一个TAG_Compound“root”和一个TAG_String“hello world”组成的小型测试用例NBT文件的URL,其值为“Bananrama”。我的目标是能够首先解析,然后从那里开始工作。 clsTagString是完整的,理论上应该可以工作。这是clsTagCompound,这被证明是一个愚蠢的,因为它作为其有效载荷任何其他标签,包括额外的TAG_compounds。泛型也许是答案,但是这些东西很难考虑。 – Kumba 2010-10-22 00:33:49
进一步的(愚蠢的评论限制),我有一个基类,clsNamedBinaryTag,它包含所有标签通用的TagType,TagName和TagNameLength字段的常见访问器属性/函数。标签也可以没有名称 - 可以通过查看TagNameLength是否为0来确定。 问题在于Payload。每个标签实现不同的有效负载,所以TAG_String存储一个以其字符串长度为前缀的字符串。 TAG_Integer存储一个32位有符号整数值。 TAG_List存储(基本上)一个单一类型的数组。 – Kumba 2010-10-22 00:37:31