数据列表
数据列表通过索引存储数据令牌,类似于 C# List。大多数数据列表函数只是底层 C# 列表的包装器,因此如果您需要更具体的细节,C# 列表文档也同样适用。
数据列表可以使用 VRCJSON 与 JSON 字符串之间进行序列化。这是当前推荐的通过网络同步数据列表的方法。
如果您使用 UdonSharp,请包含 using VRC.SDK3.Data; 指令以使用数据列表。
构造函数
| 构造函数 | 结果 |
|---|---|
| DataList() | 构造一个具有默认初始容量的空 DataList。 |
| DataList(int) | 构造一个具有指定初始容量的空 DataList。详情请参见 C# 文档。 |
| DataList(params DataToken[]) | 构造一个填充了给定 DataToken 对象的 DataList。 |
属性
| 属性 | 结果 |
|---|---|
| Capacity | 设置或获取列表的容量。详情请参见 C# 文档。 |
| Count | 获取列表中的元素数量 |
函数
| 函数 | 输入 | 输出 | 结果 |
|---|---|---|---|
| Add | DataToken | 在列表末尾添加一个令牌 | |
| AddRange | DataList | 将另一个 Data List 的值添加到此 Data List 的末尾。 | |
| BinarySearch | DataToken value | int index | 使用二分搜索算法通过比较定位 List 中的特定元素。要执行二分搜索,列表必须已排序。关于二分搜索的更多详情,请参见 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。如果 index 或 count 超出范围,则返回 false。 |
| IndexOf | DataToken item | int index | 搜索指定对象并返回整个 DataList 中第一个匹配项的从零开始的索引。如果未找到,返回 -1。 |
| IndexOf | DataToken item, int startIndex | int index | 搜索指定对象并返回 DataList 中从指定索引到最后一个元素范围内第一个匹配项的从零开始的索引。如果未找到,返回 -1。 |
| IndexOf | DataToken item, int startIndex, int count | int index | 搜索指定对象并返回 DataList 中从指定索引开始且包含指定数量元素范围内第一个匹配项的从零开始的索引。如果未找到,返回 -1。 |
| Insert | int index, DataToken input | bool success | 在列表中间插入一个令牌。指定索引之上的所有条目将上移一位。如果索引超出范围,则返回 false。 |
| InsertRange | int index, DataList input | 在 DataList 中间插入一个 DataList。指定索引之上的所有条目将上移。如果索引超出范围,则返回 false。 | |
| LastIndexOf | DataToken item | int index | 搜索指定对象并返回整个 DataList 中最后一个匹配项的从零开始的索引。如果未找到,返回 -1。 |
| LastIndexOf | DataToken item, int startIndex | int index | 搜索指定对象并返回 DataList 中从第一个元素到指定索引范围内最后一个匹配项的从零开始的索引。如果未找 到,返回 -1。 |
| LastIndexOf | DataToken item, int startIndex, int count | int index | 搜索指定对象并返回 DataList 中包含指定数量元素且结尾在指定索引范围内最后一个匹配项的从零开始的索引。如果未找到,返回 -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 包含对其他 Data Container 的引用,这些引用将保持相同。 | |
| Sort | 对列表中的所有元素进行排序。如果所有元素类型相同,则按该类型的原生比较操作进行排序。如果 DataList 包含多种不同类型但都是数字,则通过数值转换进行排序。如果 DataList 包含多种不同的非数字类型,则按以下顺序排序:Null, Number, String, DataList, DataDictionary, Reference | ||
| Sort | int index, int count | 执行与 Sort 相同的操作,但仅限于指定范围内,从提供的索引开始,向后延伸指定的数量。 | |
| ToArray | DataToken[] output | 将 DataList 转换为 DataToken 数组 | |
| TrimExcess | 如果 DataList 中的元素数量小于阈值,将容量设置为实际元素数量。 | ||
| 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($"成功!{value}");
}
else
{
Debug.Log("失败!{value}");
}
如果失败,你收到的 DataToken 仍然有效,但包含的不是你的数据,而是一个错误。
当你想要从某个位置获取某个值但不关心具体是什么时,此方法很好。
由于此函数没有内置类型检查,你应该将其与某种类型检查配对使用,无论是 if、branch 还是 switch。如果你只关心一种特定类型,则建议使用带 TokenType 的 TryGetValue 版本,它会自动进行类型检查。
带 TokenType 的 TryGetValue
如果你想从列表中获取值但不知道它可能是什么类型,进行类型检查很重要。你可以自己在代码中执行此操作,但这可能会变得混乱。相反,你可以使用包含 TokenType 的 TryGetValue 版本。这样做时,它指示你仅当值是你期望的类型时才检索该值。否则,它返回 false,并且可以优雅地处理。
此方法适用于你想从特定位置获取特定值,但数据来自外部来源,因此你不确定来源是否有正确的数据。
// 你可以这样做,但有点丑陋
if (list.TryGetValue(0, out DataToken value)) {
if (value.TokenType == TokenType.DataDictionary)
{
Debug.Log($"成功!匹配的字典有 {value.DataDictionary.Count} 个项目");
}
}
// 这种方法内置了类型检查!功能相同,但更简洁。
if (list.TryGetValue(0, TokenType.DataDictionary, out value)) {
Debug.Log($"成功!匹配的字典有 {value.DataDictionary.Count} 个项目");
}
简写括号语法
你也可以使用括号语法设置和获取 DataList 中的项目,例如在 UdonSharp 中使用 list[5] = "value"; 或在 Udon Graph 中使用 DataList Get Item 节点。此方法更小且更易于使用。但请注意,这并非完全安全,如果你尝试执行无效操作,可能会导致 udonbehaviour 停止。只有在完全控制数据且能保证数据存在并是你期望的类型时,才应使用此方法。否则,建议使用某种形式的 TryGetValue。
list[0] = 5;
list[1] = 10;
// 这假设索引 0 和 1 将始终包含整数。
// 这是一个安全的假设,因为我们刚刚在受控环境中设置了它们。
// 如果数据来自外部来源,我们不应该做这些假设!
int sum = list[0].Int + list[1].Int;
初始化 Data List
在 UdonSharp 中,Data List 可以在私有变量中初始化。这允许你拥有在代码运行之前定义的预先存在的数据集。这也支持嵌套字典和 DataTokens 支持的任何其他内容。以下是应如何使用此语法的示例:
private DataList _groceries = new DataList()
{
"Bananas",
"Grapes",
"Milk",
"Soda",
"Turkey",
"Ham",
"Roast Beef"
}
目前,UdonSharp 不支持函数内部的此类初始化器。这将是 UdonSharp 的功能请求。
目前,Unity 不会序列化 DataList,这意味着不建议将其用于序列化的公共变量。 应仅用于 private 或 [NonSerialized] public 变量。这是我们仍在努力开发的功能的补充。
与其他玩家通过网络同步 Data List
Data List 不能直接同步。但可以使用 VRCJson 序列化为 JSON 字符串或从 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());
}
}
常见问题
为什么不给每种类型都提供 ToArray?
为每种数据类型提供 ToArray 方法本来是理想的做法。然而,由于 Udon 缺乏对泛型的支持,这目前不可行。虽然技术上可以创建单独的方法,如 ToStringArray、ToFloatArray、ToDoubleArray 等,但这种方法会导致代码过度膨胀以覆盖所有可能的类型。此外,一旦 Udon 2 引入对泛型的支持,这些方法将被弃用。而且,基本的 ToArray 方法并不能提供显著的价值。真正的优势在于能够对对象类型执行 ToArray,例如 ToArray(typeof(Collider)),这样可以消除类型转换的需要。然而,支持所有可能对象的 ToArray 是不现实的,而专门支持对象的 ToArray 甚至比使用令牌更糟糕。
尽管从 DataToken 中检索值可能有些繁琐,但它们专门为此目的而设计,并具有多种实用功能来协助此任务。
数组类似,有什么区别?
数组是一种类似的结构,用于按顺序存储大量值,通过索引访问。它们也非常简单,在仅做这些事情方面非常高效。DataList 是一种更复杂 的类型,可以做更多事情。例如,数组在首次创建时必须使用特定长度初始化,除非创建新数组替换它,否则无法添加更多项。但不要被误导——仍然有充分的理由使用数组而不是列表。
何时应该使用 DataList 而不是数组?
当你需要特定功能时,才应选择 DataList 而不是数组,并非所有情况都需要切换。
- 当你想要动态地向容器添加新项或移除项时。数组不能做到这一点。
- 当你希望单个容器同时包含多种不同类型时。数组不能做到这一点。
- 当你希望容器可以任意包含其他容器时。数组可以做到这一点,但必须有严格的深度定义。DataList 可以任意深度嵌套。
何时应该使用数组而不是 DataList?
- 当性能至关重要时,例如每帧遍历容器。由于需要从令牌中提取值,DataList 可能有非常小的性能开销。
- 当你想要通过网络同步容器时。DataList 在技术上通过 JSON 支持这一点(如果你确实需要 DataList 的话),但这在性能和带宽上比普通的数组同步昂贵得多。
- 当你的容器仅需要包含一种特定类型时。DataList 当然可以做到这一点,但它们绕过了 C# 的严格类型特性。这意味着你的代码编辑器无法确切知道容器包含的类型,这可能导致你编写原本会被编译错误捕获的 bug。
- 当你想包含 Data Token 不直接支持的类型时。Data Token 可以通过对象引用和装箱包含任何类型,但这并不理想。你需要将引用取出并转换为你想要的类型。