所以我想知道在纯.NET中是否有答案来表示任意数据类型的集合。我知道有旧的,后期限制的VB6集合,但我正在寻找像Generics这样的东西,但要么无需在编译时指定类型,要么找到一种聪明的方法来允许代码自己确定类型然后调用一些泛型类。
为什么呢?我很无聊,我觉得尝试为NBT或NamedBinaryTag
实现我自己的库会很有趣。这是流行的Minecraft游戏中使用的存储格式。规范文档在这里:http://www.minecraft.net/docs/NBT.txt
我知道那里有现有的实现,但如果我只是作为一种学习经验来更好地掌握文件流,字节数组,字节序转换和一般.NET内容,那么复制那些就没有意义了。 (我过去经常使用VB6 / VBA,因此.NET是一个巨大的变化)。
让我感到震惊的是TAG_Compound。根据该规范,它本质上是任何其他Tag类型的对象的集合,包括其他嵌套的TAG_Compounds。你可以用这种格式做一些怪异的嵌套/递归。
我的头脑中有一个粗略的轮廓,如何做其他类,但任意类型的存储只是让我在如何将它存储在存根类(clsTagCompound)中绘制一个空白以便通用class(clsNBT(Of T))可以使用泛型函数来访问有效负载。
List(Of T)看起来如果我可以提供一个通用接口它可以工作。但由于Generic类将是使用的主要组件,它的接口也是通用的,这只会导致讨厌的泛型链(List(Of(clsNBT(Of XXX)))。
关于我思考的想法,提示和批评?
由于此规范适用于字节流,因此这里是未压缩的NBT文件的十六进制输出(使用其中一个Minecraft编辑器创建)。它是一个包含在TAG_Compound中的TAG_String,虽然没有特别说明,但它通常是在NBT文件中找到的第一个TAG,它封装了所有其他标签。
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
注意:我对游戏的任何mod都不太感兴趣 - 我发现NBT格式整洁而且简单到可能有用。已经在思考如何扩展格式以处理标记中的无符号类型(即TAG_UInteger),并且可能为未压缩流添加前缀为幻数(如Linux / Unix可执行文件在前四个字节中具有“ELF”)。这可以防止这些工具中的任何问题被用于打开任意/意外的数据格式(我也可能会将这些想法传递给游戏开发者)。
EDIT2:所以我改变了方向。 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'ed到INbt(Of T)将其转换为定义该特定TagType的类。
但这似乎没有按计划运作。我相信我的Catch 22甚至可以使用clsTagCompound,我仍然必须定义T,这就是我再次卡住的地方。我不知何故需要创建一个非通用的接口,但可以应用于各种Tag类型的所有类,并仍然调用它们的GetPayload
函数来返回特定于特定标记的有效负载。
答案 0 :(得分:3)
如果要使用可以容纳任何内容的集合类型,可以使用非泛型集合(在System.Collections
namespace中)或通用集合(Of Object)
。
如果要在运行时确定对象的类型,可以使用GetType
method(以匹配确切类型)或Typeof [something] Is [sometype]
构造来匹配实现/继承/是给定的接口/类。 More explanations on MSDN.
对于您的问题,我会编写一个通用接口ITagElement
,它将由TagCompound
,TagString
,TagList
等类实现。至于TagList
类,我会让它继承System.Collections.ObjectModel
namespace中的一个类。
答案 1 :(得分:0)
您可以将object或varientType类型用于存储任意对象集的列表。
要在运行时找到对象类型,可以使用反射。我从来没有在vb.net中使用过它,但它得到了支持。
答案 2 :(得分: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
List(Of clsTagCompound)
中的对象可以是来自任何继承clsTagCompound
的类的对象组合。
答案 3 :(得分:0)
在处理固定格式的数据包时,我使用这样的东西。这也说明了修复“endianess”的简便方法。我解码了前几个字节(使用你提供的样本),这样你就可以得到一个想法。
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