Data Lists
Data Listsは、C# Listsと同様に、インデックスによって Data Tokens を格納します。Data Listの関数のほとんどは、基盤となるC#リストのラッパーであるため、より詳細な情報が必要な場合はC#リストのドキュメントも併せて参照してください。
Data Listsは、VRCJSON を使用してJSON文字列との間でシリアライズを行うことができます。これが、現在ネットワーク経由でData Listsを同期する際に推奨される方法です。
UdonSharpを使用している場合は、using VRC.SDK3.Data; ディレクティブを含めることでData Listsを使用できます。
コンストラクタ
| コンストラクタ | 結果 |
|---|---|
| DataList() | デフォルトの初期容量で空の DataList を構築します。 |
| DataList(int) | 指定された初期容量で空の DataList を構築します。詳細については、C# のドキュメントを参照してください。 |
| DataList(params DataToken[]) | 指定された DataToken オブジェクトで満たされた DataList を構築します。 |
プロパティ
| プロパティ | 結果 |
|---|---|
| Capacity | リストの容量を設定または取得します。詳細は C# ドキュメント を参照してください。 |
| Count | リスト内の要素数を取得します |
Functions
| Function | Input | Output | Result |
|---|---|---|---|
| Add | DataToken | リストの末尾にトークンを追加します。 | |
| AddRange | DataList | 別の Data List の値をこの Data List の末尾に追加します。 | |
| BinarySearch | DataToken value | int index | バイナリサーチ(二分探索)アルゴリズムを使用して、比較によりリスト内の特定の要素を検索します。 バイナリサーチを実行するには、リストがソート済みである必要があります。バイナリサーチの詳細については、C# のドキュメントを参照してください。 |
| BinarySearch | DataToken value, int startIndex, int count | int index | 指定されたインデックスから開始し、指定されたカウント数分だけリストの末尾方向に範囲を限定してバイナリサーチを実行します。この関数は比較によって検索するため、辞書やリストを比較する際は内容ではなく要素数で比較される点に注意してください。そのため、リスト内の特定の辞書やリストを見つける用途には適していません。 |
| Clear | このリストからすべての値を削除します。 | ||
| Contains | DataToken value | bool result | Data List に指定された値が含まれている場合、true を返します。 |
| DeepClone | DataList result | DataList を複製し、すべての値を含む新しい DataList を作成します。これはディープクローンを実行するため、各 DataList や DataDictionary の内部を再帰的に探索し、その内容もコピーします。ただし、配列などの他の構造体の内部までは探索しないため、それらは元のデータと同じ参照を保持します。 | |
| GetRange | int index, int count | DataList output | DataList の一部を別の DataList にコピーします。インデックスやカウントが範囲外の場合は false を返します。 |
| IndexOf | DataToken item | int index | 指定したオブジェクトを検索し、DataList 全体の中で最初に見つかった位置のインデックス(0 から始まる)を返します。見つからない場合は -1 を返します。 |
| IndexOf | DataToken item, int startIndex | int index | 指定したオブジェクトを検索し、指定されたインデックスからリストの最後までの範囲内で最初に見つかった位置のインデックス(0 から始まる)を返します。見つからない場合は -1 を返します。 |
| IndexOf | DataToken item, int startIndex, int count | int index | 指定したオブジェクトを検索し、指定されたインデックスから始まる指定した要素数分の範囲内で最初に見つかった位置のインデックス(0 から始まる)を返します。見つからない場合は -1 を返します。 |
| Insert | int index, DataToken input | bool success | リストの中間にトークンを挿入します。指定されたインデックス以降のすべてのエントリが 1 つ後ろにずれます。インデックスが範囲外の場合は false を返します。 |
| InsertRange | int index, DataList input | DataList の中間に別の DataList を挿入します。指定されたインデックス以降のすべてのエントリが後ろにずれます。インデックスが範囲外の場合は false を返します。 | |
| LastIndexOf | DataToken item | int index | 指定したオブジェクトを検索し、DataList 内で最後に見つかった位置のインデックス(0 から始まる)を返します。見つからない場合は -1 を返します。 |
| LastIndexOf | DataToken item, int startIndex | int index | 指定したオブジェクトを検索し、リストの先頭から指定されたインデックスまでの範囲内で最後に見つかった位置のインデックス(0 から始まる)を返します。見つからない場合は -1 を返します。 |
| LastIndexOf | DataToken item, int startIndex, int count | int index | 指定したオブジェクトを検索し、指定されたインデックスで終了する指定した要素数分の範囲内で最後に見つかった位置のインデックス(0 から始まる)を返します。見つからない場合は -1 を返します。 |
| Remove | DataToken value | bool success | 指定した値の最初の出現を削除します。一致する値が見つかった場合は true を、見つからなかった場合は false を返します。 |
| RemoveAll | DataToken value | bool success | 指定した値のすべての出現を削除します。一致する値が見つかった場合は true を、見つからなかった場合は false を返します。 |
| RemoveAt | int index | 指定したインデックスにある要素を削除します。 | |
| RemoveRange | int index, int count | リストから指定された範囲の要素を削除します。 | |
| Reverse | リスト内のすべての要素の順序を反転させます。 | ||
| Reverse | int index, int count | 指定されたインデックスから始まり、指定されたカウント数分だけリストの末尾方向に範囲を限定して、その範囲内の要素の順序を反転させます。 | |
| SetValue | int index, DataToken input | 指定したインデックスに DataToken を設定します。 | |
| ShallowClone | DataList result | DataList を複製し、すべての値を含む新しい DataList を作成します。これはシャロークローン(浅いコピー)を行うため、DataList が他のデータコンテナへの参照を含んでいる場合、それらは同じ参照を保持したままとなります。 | |
| Sort | リスト内のすべての要素をソートします。すべての要素が同じ型である場合、その型のネイティブな比較操作に基づいてソートされます。DataList に複数の異なる型が含まれているものの、それらがすべて数値である場合は、数値変換を行ってソートされます。DataList に数値以外の型が混在している場合は、次の順序でソートされます: Null, Number, String, DataList, DataDictionary, Reference |
||
| Sort | int index, int count | Sort と同じ操作を実行しますが、指定されたインデックスから始まり、指定されたカウント数分だけリストの末尾方向に範囲を限定してソートを行います。 | |
| ToArray | DataToken[] output | DataList を DataToken 配列に変換します。 | |
| TrimExcess | 要素数が閾値よりも少ない場合、DataList の容量(capacity)を実際の要素数に合わせます。 | ||
| TryGetValue | int index | DataToken output | 指定したインデックスからトークンを取得し、out DataToken に格納します。成功した場合は true を返します。 |
| TryGetValue | int index, TokenType expected | bool success, DataToken output | 指定したインデックスからトークンを取得し、out DataToken に格納します。成功した場合は true を返します。このバージョンの TryGetValue には TokenType が含まれており、自動的に型チェックが行われます。型が一致しない場合は false を返し、DataError.TypeMismatch となります。 |
Jsonから生成されたData Listに対して Contains、IndexOf、LastIndexOf といった、すべての値を操作または参照する関数を呼び出すと、まだ解析されていないトップレベルの値がすべて解析される点に注意してください。値が多数ある場合、これには高いコストがかかる可能性があります。一度解析されれば、その後の操作は低コストになります。
DataList から値を取得する
DataList から値を取得する方法はいくつかあります。それぞれ用途が異なるため、目的に合わせて最適なものを選んでください。
TryGetValue
リストから安全に値を取得したい場合は、TryGetValue を使用することを推奨します。これは値の取得に成功したかどうかによって true または false を返す関数です。この関数は if や branch の条件式内に入れ、成功時と失敗時の挙動を明確にするための設計になっています。
if (list.TryGetValue(0, out DataToken value))
{
Debug.Log($"Success! {value}");
}
else
{
Debug.Log("Failed! {value}");
}
もし取得に失敗した場合でも、受け取る DataToken 自体は有効ですが、データが含まれている代わりに error が格納されます。
このメソッドは、特定の場所から値を取得したいが、その内容が何であるかは(現時点では)問わない場合に適しています。
この関数には型チェック機能が組み込まれていないため、if、branch、または switch といった何らかの型チェックを併用してください。もし特定の型のみを扱いたい場合は、自動的に型チェックを行ってくれる TokenType 指定版の TryGetValue を使用することを推奨します。
TokenType を指定した TryGetValue
リストから値を取得したいものの、どのような型であるかわからない場合は、型チェックが重要です。自前でコードを記述することもできますが、複雑になりがちです。代わりに、TokenType を引数に含むバージョンの TryGetValue を使用してください。これを使用すると、期待する型である場合にのみ値が取得されます。そうでない場合は false が返されるため、適切に処理を行うことができます。
このメソッドは、特定の場所から特定の値を取得したいが、データが外部ソース由来であり、正しいデータが格納されているか確信が持てない場合に適しています。
// You could do it this way, but it's a bit ugly
if (list.TryGetValue(0, out DataToken value)) {
if (value.TokenType == TokenType.DataDictionary)
{
Debug.Log($"Success! Matching dictionary has {value.DataDictionary.Count} items");
}
}
// This approach has a type check built in! It's functionally the same, but streamlined.
if (list.TryGetValue(0, TokenType.DataDictionary, out value)) {
Debug.Log($"Success! Matching dictionary has {value.DataDictionary.Count} items");
}
短縮形のブラケット構文
UdonSharp での list[5] = "value"; や、Udon Graph での DataList Get Item ノードといったブラケット構文を使用して、DataList のアイテムを取得・設定することも可能です。この方法は記述が短く、簡単に利用できます。ただし、これは完全な安全性は保証されておらず、無効な操作を行うと UdonBehaviour が停止する可能性がある点に注意してください。データの内容を完全に把握しており、確実に存在し、かつ期待する型であることが保証されている場合にのみ使用してください。それ以外の場合は、何らかの形の TryGetValue を使用することを推奨します。
list[0] = 5;
list[1] = 10;
// This makes the assumption that index 0 and 1 will always contain integers.
// This is a safe assumption to make since we set them just above in a controlled environment.
// If the data is coming from an external source, we shouldn't make these assumptions!
int sum = list[0].Int + list[1].Int;
Data Listの初期化
Udonsharpでは、Data Listはプライベート変数内で初期化できます。これにより、コードが実行される前に定義された既存のデータセットを持つことができます。また、ネストされた辞書やDataTokenがサポートするその他すべての型にも対応しています。この構文の使用例を以下に示します。
private DataList _groceries = new DataList()
{
"Bananas",
"Grapes",
"Milk",
"Soda",
"Turkey",
"Ham",
"Roast Beef"
}
現時点では、Udonsharpは関数内部でのこのような初期化をサポートしていません。これはUdonsharpに対する機能リクエストとなります。
また、現時点ではUnityはDataListをシリアライズしないため、シリアライズされるpublic変数に対してこれを使用することは推奨されません。 これはあくまで private または [NonSerialized] public の変数に対してのみ使用してください。これは現在開発中の機能への追加要素となります。
他のプレイヤーとネットワーク越しにデータリストを同期する
Data Listは直接同期することはできません。しかし、VRCJson を使用してJSON文字列との間でシリアライズを行うことが可能です。これが現在のUdonSyncでData Listを同期するための推奨される手法です。
このための方法の一つとして、OnPreSerializationおよびOnDeserializationを使用してJSON文字列のシリアライズとデシリアライズを行う手法があります。この手法を使えば、コードの残りの部分でシリアライズを意識する必要がなくなり、単に値を設定するだけで済みます。
[UdonSynced]
private string _json;
private DataList _list;
public override void OnPreSerialization()
{
if (VRCJson.TrySerializeToJson(_list, JsonExportType.Minify, out DataToken result))
{
_json = result.String;
}
else
{
Debug.LogError(result.ToString());
}
}
public override void OnDeserialization()
{
if(VRCJson.TryDeserializeFromJson(_json, out DataToken result))
{
_list = result.DataList;
}
else
{
Debug.LogError(result.ToString());
}
}
FAQ
なぜ各型用の ToArray メソッドがないのですか?
各データ型に対して ToArray メソッドがあれば理想的ですが、現状の Udon ではジェネリクスがサポートされていないため実現できません。ToStringArray、ToFloatArray、ToDoubleArray などの個別のメソッドを作成することは技術的に可能ですが、すべての型に対応させようとするとコードが肥大化してしまいます。さらに、Udon 2 でジェネリクスが導入されれば、これらのメソッドはすぐに非推奨となってしまいます。また、単純な ToArray メソッドだけでは、それほど大きな利点は得られません。真の利点は ToArray(typeof(Collider)) のようにオブジェクト型に対して ToArray を実行し、キャストの必要性をなくすことにありますが、あらゆるオブジェクトに対して ToArray をサポートするのは現実的ではありません。また、object 専用の ToArray をサポートすることは、DataToken を扱うよりもさらに手間がかかることになります。
DataToken から値を取得する作業は多少煩雑に感じるかもしれませんが、これこそが DataToken の本来の設計用途であり、その作業を支援するためのユーティリティがいくつか用意されています。
配列と似ていますが、何が違うのですか?
配列(Array)は、多数の値を順番に格納し、インデックスでアクセスするための似たような構造です。配列は非常にシンプルで、その役割を果たすことに特化しており、非常に効率的です。一方、DataList はより多くのことができる複雑な型です。例えば、配列は最初に作成する際に特定の長さを指定する必要があり、新しい配列に置き換えない限りアイテムを追加することはできません。しかし、だからといって配列が劣っているわけではなく、リストよりも配列を使うべき正当な理由は依然として存在します。
どのような場合に配列ではなく DataList を使うべきですか?
配列ではなく DataList を選ぶべきなのは、特定の機能が必要な場合です。すべてを DataList に置き換える必要はありません。
- コンテナに対して動的にアイテムを追加・削除したい場合。配列では不可能です。
- 1つのコンテナに複数の異なる型を同時に格納したい場合。配列では不可能です。
- コンテナの中にコンテナを任意に含めたい場合。配列でも可能ですが、厳密な深さを定義する必要があります。
DataListなら、必要に応じて任意の深さにネストできます。
どのような場合に DataList ではなく配列を使うべきですか?
- 毎フレームコンテナを反復処理するなど、パフォーマンスが重要な場合。
DataListはDataTokenから値を取り出す際に、ごくわずかなパフォーマンスオーバーヘッドが発生する可能性があります。 - コンテナをネットワーク経由で同期したい場合。どうしても
DataListが必要な場合は JSON を介して技術的に同期可能ですが、通常の配列同期よりもパフォーマンスと帯域幅の両面で負荷がはるかに大きくなります。 - コンテナに特定の1つの型のみを格納すればよい場合。
DataListでも可能ですが、C# の厳密な型付けの特性をバイパスすることになります。つまり、コードエディタがコンテナに含まれる型を正確に把握できなくなり、本来であればコンパイルエラーになるようなバグを書いてしまう可能性があります。 DataTokenで直接サポートされていない型を格納したい場合。DataTokenはオブジェクト参照やボックス化を通じてあらゆる型を格納できますが、理想的ではありません。参照を取り出して、目的の型にキャストする必要があります。
最終更新: