Variant: типы, проверка данных и общие контейнеры
Строгое чтение данных C#, числовые границы, Nil, копирование и время жизни.
Variant хранит одно значение движка вместе с меткой его типа. Он нужен на динамических границах API: в свойствах, вызовах методов, сигналах и коллекциях движка. Это не замена каждой переменной C#, не контейнер для произвольного CLR-объекта и не формат файла сохранения.
Обычные поля игровой логики оставляйте типизированными. Получив Variant из API или контейнера данных, сначала проверьте тип и ограничения значения, затем преобразуйте его.
Учитывайте хранимый тип
| Значение C# | Метка движка | Как читать |
|---|---|---|
| default(Variant), null вместо объекта движка | Nil | Obj возвращает null. Пустая строка и пустая коллекция — другие значения. |
| bool | Bool | AsBool. Не принимайте его незаметно вместо целочисленного поля. |
| int, long | Int | Хранится знаковое 64-битное целое; Obj упаковывает его как long. |
| float, double | Float | Хранится число двойной точности; Obj упаковывает его как double. Расширение не восстанавливает точность исходного float. |
| string | String | AsString. Это текст, а не автоматически проверенное число для вашей схемы. |
| Vector2 и другие поддерживаемые типы значений движка | Соответствующая метка | Читайте подходящий тип; у Vector2I и Vector2 разные метки. |
| Экземпляр наследника ReckfellObject | Object | Идентичность объекта, а не его клон или гарантия вечного существования. |
| Reckfell.Collections.Array / Dictionary | Array / Dictionary | Извлечение сохраняет общий контейнер. |
| int[] | PackedInt32Array | Преобразование копирует содержимое управляемого массива; AsInt32Array возвращает управляемую копию. |
Проверяйте хранимую метку движка через VariantType. Выражение value.Obj is int не подходит для проверки Variant Int: Obj возвращает long. Аналогично Variant из float даёт упакованный double.
Нативный Variant C++ и оболочка C# работают с одними категориями значений движка, но отличаются представлением в памяти и управлением временем жизни. Размер нативной структуры не описывает стоимость выделений .NET. List<T> или произвольный класс C# не становятся автоматически Array движка или допустимым содержимым Variant; сначала создайте поддерживаемое значение. Обобщённые From<T>/As<T> также требуют поддерживаемого типа.
Преобразование не заменяет проверку
AsInt32, явное приведение к int и As<int>() выполняют преобразование. Они не гарантируют, что исходная метка была Int и что значение помещалось в целевой тип. В установленном движке "42" преобразуется в 42, true — в 1, а 3.75 — в целое 3. Int со значением 2147483648 сужается через AsInt32 до -2147483648.
Если поле должно содержать именно 32-битное целое, сначала проверьте метку и диапазон 64-битного значения. Для позиции определите, допустимы ли неконечные компоненты. Этот помощник отклоняет NaN/бесконечность и требует именно Vector2:
using Reckfell;
public static class VariantBoundary
{
public static bool TryGetInt32(Variant value, out int result)
{
result = 0;
if (value.VariantType != Variant.Type.Int)
return false;
long number = value.AsInt64();
if (number < int.MinValue || number > int.MaxValue)
return false;
result = (int)number;
return true;
}
public static bool TryGetFiniteVector2(Variant value, out Vector2 result)
{
result = Vector2.Zero;
if (value.VariantType != Variant.Type.Vector2)
return false;
Vector2 vector = value.AsVector2();
if (!vector.IsFinite())
return false;
result = vector;
return true;
}
}Сохраните его как VariantBoundary.cs. Вызовите VariantBoundary.TryGetInt32(value, out int count) для целочисленного значения или VariantBoundary.TryGetFiniteVector2(value, out Vector2 position) для конечной позиции. False означает несоответствие этому контракту; выходное значение равно нулю. Затем добавьте ограничения предметной области: например, неотрицательное количество или положение внутри уровня.
Эта политика намеренно отклоняет числовую строку, Float без дробной части и Vector2I. Если ваш формат разрешает такие преобразования, реализуйте и опишите отдельные правила вместо случайного принятия всех преобразований движка.
Отличайте отсутствующий ключ от Nil
Используйте Dictionary.TryGetValue, если отсутствие ключа имеет значение. False означает, что ключа нет. True с VariantType Nil означает, что ключ есть и явно содержит Nil. Проверьте оба результата до подстановки значения по умолчанию.
Индексатор Dictionary подходит для заведомо существующего ключа; это не универсальная проверка наличия. Определите, означает ли отсутствие значения настройку по умолчанию, ошибочные данные или другую версию схемы. Не сводите все три случая к нулю.
Различайте копию и общий контейнер
Извлечение Array или Dictionary из Variant создаёт ещё одну оболочку того же контейнера движка. Изменения видны через другие ссылки. Присваивание оболочки другой переменной C# не создаёт независимое содержимое.
Array.Duplicate(false) создаёт новый внешний массив, сохраняя общими вложенные массивы и словари. Duplicate(true) отделяет вложенные контейнеры, но объекты движка остаются ссылками: Entities и игровой мир не клонируются. Для создания объектов применяйте подходящее API инстанцирования или дублирования объектов/ресурсов.
Чтение элемента Array копирует нативное значение Variant. Последующая замена целочисленного элемента не переписывает ранее прочитанное целое. Если прочитанное значение содержит контейнер, его содержимое по-прежнему может быть общим. Копирование оболочки значения и копирование содержимого по ссылке — разные операции.
У управляемых упакованных массивов другое поведение: проверенное преобразование int[] и его извлечение копируют содержимое. Изменение исходного или полученного int[] не редактирует упакованное значение, уже находящееся в Variant. Если нужны изменения, явно назначьте обновлённые данные.
Соблюдайте владение и время жизни объектов
Variant Object сохраняет идентичность объекта. Entity можно явно освободить, даже если на него ссылается Variant. Перед использованием потенциально устаревшего объекта проверяйте IsInstanceValid: метка, отличная от Nil, не доказывает, что объект жив. У RefCounted используется подсчёт ссылок, отличающийся от явного времени жизни Entity и управления деревом.
Оболочка C# Variant реализует IDisposable. Для собственного временного значения, например результата чтения элемента коллекции, using позволяет своевременно освободить нативные данные. Не освобождайте заимствованные значения вызывающего кода: обычное присваивание структуры C# может сохранять общее управление освобождением и не создаёт второго независимого владельца. Не обращайтесь к копиям после освобождения общих данных.
Выполненный пример проверяет метки, числовые преобразования и границы, конечность Vector2, копии управляемых упакованных массивов, общие контейнеры, вложенное дублирование, отсутствующие/Nil-значения и время жизни Entity. Все перегрузки преобразований, произвольные неверные нативные приведения, сериализация и конкурентное изменение отдельно не проверены. Доступные операции перечислены в API Variant; формат сериализации выбирайте отдельно.