2010-10-21 68 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