Проверка типа переменной, коллекции и ссылки во встроенном языке 1С:Предприятия 8.3
Содержание
Тип значения переменной в 1С 8.3 определяют функцией ТипЗнч(), а результат сравнивают с Тип("ИмяТипа"): Если ТипЗнч(Параметр) = Тип("Структура") Тогда. Для проверки «входит ли тип в набор» служит ОписаниеТипов.СодержитТип(), для ссылочных объектов — Справочники.ТипВсеСсылки() и Метаданные.НайтиПоТипу(). Это механизм платформы, он одинаков в любой конфигурации: БП, ЗУП, УТ или самописной.
Типичная ситуация: в общий модуль приходит параметр, который в одном месте передают строкой, в другом — ссылкой на контрагента, в третьем — массивом ссылок. Функция падает с ошибкой «Поле объекта не обнаружено» на чужих данных. Решение одно — проверить тип объекта до того, как обращаться к его полям.
ТипЗнч и Тип: как определить тип переменной
Функция ТипЗнч(Значение) возвращает значение типа Тип. Это не строка, а специальный объект-дескриптор. Его сравнивают с другим дескриптором, который создаёт функция Тип() по имени типа.
Значение = 125.5;
Если ТипЗнч(Значение) = Тип("Число") Тогда
Сообщить("Это число");
ИначеЕсли ТипЗнч(Значение) = Тип("Строка") Тогда
Сообщить("Это строка");
ИначеЕсли ТипЗнч(Значение) = Тип("Дата") Тогда
Сообщить("Это дата");
КонецЕсли;
Имена типов для Тип() пишутся так же, как в синтакс-помощнике:
| Что проверяем | Выражение |
|---|---|
| 🔢 Примитивы | Тип("Число"), Тип("Строка"), Тип("Дата"), Тип("Булево") |
| ⛔ Пустые значения | Тип("Неопределено"), Тип("Null") |
| 📦 Коллекции | Тип("Массив"), Тип("Структура"), Тип("Соответствие"), Тип("ТаблицаЗначений"), Тип("СписокЗначений") |
| 🔗 Ссылка | Тип("СправочникСсылка.Номенклатура"), Тип("ДокументСсылка.РеализацияТоваровУслуг") |
| 🧱 Объект | Тип("СправочникОбъект.Номенклатура"), Тип("ДокументОбъект.РеализацияТоваровУслуг") |
| 🆔 Прочее | Тип("УникальныйИдентификатор"), Тип("ХранилищеЗначения") |
Почему нельзя сравнивать строковое представление
Соблазн написать Если Строка(ТипЗнч(Значение)) = "Число" понятен, но это ловушка. Представление типа зависит от языка интерфейса и синонимов в метаданных. Переименовали синоним справочника — проверка тихо перестала работать.
Сравнивайте дескрипторы: ТипЗнч(А) = Тип("...") или ТипЗнч(А) = ТипЗнч(Б). Строка годится только для вывода в журнал или сообщение.
Опечатка в имени типа
Если в Тип() передать несуществующее имя, например Тип("СправочникСсылка.Номенклатурa") с латинской «a», платформа выбросит исключение во время выполнения, а не при синтаксической проверке. Синтаксический контроль модуля такую ошибку не ловит. Поэтому имена типов лучше выносить в одну функцию или проверять тестом на копии базы.
Сравнение типов: есть ли в 1С оператор «ЕСТЬ»
Во встроенном языке 1С отдельного оператора вроде is из C# нет. Проверка типа — это всегда сравнение через = или <>. Слово «ЕСТЬ» существует только в языке запросов, о нём ниже.
Когда допустимых типов несколько, цепочка ИЛИ разрастается. Удобнее собрать их в ОписаниеТипов:
ДопустимыеТипы = Новый ОписаниеТипов("Число, Строка, Дата");
Если ДопустимыеТипы.СодержитТип(ТипЗнч(Значение)) Тогда
// значение одного из трёх типов
КонецЕсли;
У ОписаниеТипов три метода, которые пригодятся постоянно:
- ✅
СодержитТип(Тип)— входит ли тип в описание, возвращаетБулево; - 📋
Типы()— массив всех типов описания, удобно для составных реквизитов; - 🔄
ПривестиЗначение(Значение)— приводит значение к типу описания; если привести нельзя, вернёт значение по умолчанию (0 для числа, пустую строку для строки).
Приведение к типу с квалификаторами
ОписаниеТипов умеет не только проверять, но и нормализовать значение. Например, нужно гарантированно получить число с двумя знаками после запятой и не длиннее 15 разрядов:
ТипСумма = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2));
Сумма = ТипСумма.ПривестиЗначение(125.456); // число с двумя знаками после запятой
Пусто = ТипСумма.ПривестиЗначение(Неопределено); // 0
Строки так же обрезаются по длине через КвалификаторыСтроки. Это удобно перед записью в реквизит ограниченной длины: значение не вызовет ошибку, а будет укорочено.
Приведение молча меняет данные. Для сумм в учёте потеря лишних знаков без предупреждения может дать расхождение в копейках, поэтому для проверок сначала сравнивайте тип, а приводите только там, где это ожидаемо.
Составной тип реквизита
Тип реквизита в метаданных — это тоже ОписаниеТипов. Так можно узнать, какие типы допускает поле, не открывая конфигуратор:
&НаСервере
Процедура ПоказатьТипыРеквизита()
ОписаниеРеквизита = Метаданные.Документы.РеализацияТоваровУслуг.Реквизиты.Контрагент;
Для Каждого ТекТип Из ОписаниеРеквизита.Тип.Типы() Цикл
Сообщить(Строка(ТекТип));
КонецЦикла;
КонецПроцедуры
Имена документа и реквизита замените на свои: в каждой конфигурации они разные. Колонки таблицы значений устроены так же: у колонки есть свойство ТипЗначения с тем же ОписаниеТипов.
Массивы, структуры и соответствия: проверка типа коллекции
Коллекции проверяют тем же ТипЗнч. Подвох в фиксированных версиях: ФиксированныйМассив, ФиксированнаяСтруктура и ФиксированноеСоответствие — отдельные типы. Структура из параметров формы или из кеша повторного использования часто приходит фиксированной, и проверка на Тип("Структура") даёт Ложь.
ТипыСтруктур = Новый ОписаниеТипов("Структура, ФиксированнаяСтруктура");
Если ТипыСтруктур.СодержитТип(ТипЗнч(Параметры)) Тогда
// можно читать свойства
КонецЕсли;
Что лежит внутри коллекции
Тип элементов коллекции платформа не фиксирует: в один массив можно положить число, строку и ссылку. Проверять приходится каждый элемент.
Для Каждого Элемент Из МассивЗначений Цикл
Если ТипЗнч(Элемент) <> Тип("СправочникСсылка.Контрагенты") Тогда
ВызватьИсключение СтрШаблон("Недопустимый элемент: %1", Строка(Элемент));
КонецЕсли;
КонецЦикла;
Структура: есть ли ключ и какого типа значение
Обращение к отсутствующему ключу Структура.Поле вызывает ошибку. Безопасный путь — метод Свойство(), он возвращает Булево и сразу кладёт значение во вторую переменную:
Сумма = Неопределено;
Если Настройки.Свойство("Сумма", Сумма) И ТипЗнч(Сумма) = Тип("Число") Тогда
// ключ есть, значение числовое
КонецЕсли;
У Соответствие метод Получить(Ключ) при отсутствии ключа возвращает Неопределено, исключения не будет. Но если в соответствии под ключом специально лежит Неопределено, эти два случая не различить. Для таких данных проверяйте наличие ключа обходом или храните маркер другого типа.
| Коллекция | Чем безопасно читать | Что вернёт при отсутствии |
|---|---|---|
| Структура | Свойство(Ключ, Значение) | Ложь, значение не меняется |
| Соответствие | Получить(Ключ) | Неопределено |
| Массив | Найти(Значение) | Неопределено вместо индекса |
Ссылочные объекты и метаданные: как узнать вид объекта
Для ссылок обычно нужно ответить на один из трёх вопросов: ссылка ли это вообще, какой вид объекта (справочник, документ) и какой конкретно объект метаданных.
Это ссылка или нет
Менеджеры коллекций объектов возвращают описание всех ссылочных типов своего вида через ТипВсеСсылки():
ТипЗначения = ТипЗнч(Значение);
ЭтоСправочник = Справочники.ТипВсеСсылки().СодержитТип(ТипЗначения);
ЭтоДокумент = Документы.ТипВсеСсылки().СодержитТип(ТипЗначения);
Метод есть и у других менеджеров: Перечисления, ПланыВидовХарактеристик, ПланыСчетов, ПланыОбмена, БизнесПроцессы, Задачи. В типовых конфигурациях на БСП то же самое уже собрано в функцию ОбщегоНазначения.ЭтоСсылка(Тип) — она принимает тип, а не значение.
Какой это объект метаданных
Метаданные.НайтиПоТипу(Тип) возвращает объект метаданных или Неопределено, если тип не связан с метаданными (число, строка, массив). Дальше берём имя:
&НаСервере
Функция ИмяОбъектаПоЗначению(Значение)
ОбъектМД = Метаданные.НайтиПоТипу(ТипЗнч(Значение));
Если ОбъектМД = Неопределено Тогда
Возврат "";
КонецЕсли;
Возврат ОбъектМД.ПолноеИмя(); // например, "Документ.РеализацияТоваровУслуг"
КонецФункции
Если ссылка уже есть, короче вызвать у неё метод Метаданные(): Ссылка.Метаданные().Имя. Тот же метод есть у объекта (ДокументОбъект).
Ссылка и объект — разные типы
ДокументСсылка.РеализацияТоваровУслуг и ДокументОбъект.РеализацияТоваровУслуг не равны друг другу. В обработчике ПередЗаписью модуля объекта приходит объект, в параметре команды формы списка — ссылка. Универсальная процедура должна учитывать оба варианта:
Если Документы.ТипВсеСсылки().СодержитТип(ТипЗнч(Источник)) Тогда
Ссылка = Источник;
Иначе
Ссылка = Источник.Ссылка; // пришёл объект
КонецЕсли;
У нового, ещё не записанного объекта Ссылка пустая. Перед работой с ней проверьте Ссылка.Пустая() или Объект.ЭтоНовый().
Неопределено, Null и пустые ссылки: что делать до проверки типа
Три «пустых» состояния путают чаще всего:
- ⚪
Неопределено— значение не задано: необязательный параметр без значения,Получить()по отсутствующему ключу, пустой реквизит составного типа; - ⚫
Null— приходит из запроса, когда при левом соединении нет пары, или из поля, которое для части строк не имеет смысла; - 🔘 пустая ссылка — тип ссылочный, но ссылка ни на что не указывает:
Справочники.Контрагенты.ПустаяСсылка().
Тип пустой ссылки сохраняется. ТипЗнч(Справочники.Контрагенты.ПустаяСсылка()) равен Тип("СправочникСсылка.Контрагенты"), поэтому проверка типа её пропустит. Отдельно нужна проверка заполненности:
Если ТипЗнч(Контрагент) = Тип("СправочникСсылка.Контрагенты")
И ЗначениеЗаполнено(Контрагент) Тогда
// можно читать реквизиты
КонецЕсли;
ЗначениеЗаполнено() возвращает Ложь для Неопределено, Null, пустой ссылки, нуля, пустой строки, пустой даты и пустой коллекции. Для чисел это иногда мешает: ноль может быть законным значением. Там проверяйте ТипЗнч(Значение) = Тип("Число") без ЗначениеЗаполнено.
Исключения при преобразовании
Число("12а") и Дата("20261340") выбрасывают исключение. Когда строка приходит из файла или от пользователя, преобразование оборачивают в Попытка:
Функция СтрокаВЧисло(Знач Текст, ЧислоПоУмолчанию = 0)
Текст = СтрЗаменить(СокрЛП(Текст), " ", "");
Попытка
Возврат Число(Текст);
Исключение
Возврат ЧислоПоУмолчанию;
КонецПопытки;
КонецФункции
Попытка не заменяет проверку типа. Если тип известен заранее, ТипЗнч дешевле и понятнее: исключение маскирует любые ошибки внутри блока, а не только ожидаемую.
Проверка типа в запросе: ССЫЛКА, ТИПЗНАЧЕНИЯ и ЕСТЬ NULL
В языке запросов свои средства. Здесь и появляется слово «ЕСТЬ» — в конструкции ЕСТЬ NULL.
| Задача | Конструкция запроса |
|---|---|
| Поле составного типа ссылается на нужный документ | Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг |
| Сравнить тип поля | ТИПЗНАЧЕНИЯ(Субконто1) = ТИП(Справочник.Номенклатура) |
| Отобрать пустые значения | Поле ЕСТЬ NULL или ЕСТЬNULL(Поле, 0) |
| Привести поле составного типа | ВЫРАЗИТЬ(Субконто1 КАК Справочник.Номенклатура) |
ВЫБРАТЬ
Движения.Регистратор,
ЕСТЬNULL(Движения.Сумма, 0) КАК Сумма
ИЗ
РегистрНакопления.Продажи КАК Движения
ГДЕ
Движения.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг
Имя регистра и документа подставьте из своей конфигурации. Проверка типа в запросе обычно выгоднее, чем выбрать всё и фильтровать циклом: на сервер не тянутся лишние строки, а при обращении через точку к полю составного типа ВЫРАЗИТЬ избавляет от лишних соединений со всеми таблицами типа.
Валидация входных данных: готовые примеры
Ниже функция, которую можно положить в общий серверный модуль и вызывать в начале любой процедуры, принимающей «что угодно». Она проверяет обязательные ключи структуры и их типы.
// Схема: Структура, где ключ — имя параметра, значение — ОписаниеТипов.
Функция ОшибкиПараметров(Параметры, Схема) Экспорт
Ошибки = Новый Массив;
ТипыСтруктур = Новый ОписаниеТипов("Структура, ФиксированнаяСтруктура");
Если Не ТипыСтруктур.СодержитТип(ТипЗнч(Параметры)) Тогда
Ошибки.Добавить("Параметры должны быть структурой");
Возврат Ошибки;
КонецЕсли;
Для Каждого Правило Из Схема Цикл
Значение = Неопределено;
Если Не Параметры.Свойство(Правило.Ключ, Значение) Тогда
Ошибки.Добавить(СтрШаблон("Нет параметра %1", Правило.Ключ));
ИначеЕсли Не Правило.Значение.СодержитТип(ТипЗнч(Значение)) Тогда
Ошибки.Добавить(СтрШаблон("Параметр %1: недопустимый тип %2",
Правило.Ключ, Строка(ТипЗнч(Значение))));
КонецЕсли;
КонецЦикла;
Возврат Ошибки;
КонецФункции
Вызов:
Схема = Новый Структура;
Схема.Вставить("Организация", Новый ОписаниеТипов("СправочникСсылка.Организации"));
Схема.Вставить("ДатаНачала", Новый ОписаниеТипов("Дата"));
Схема.Вставить("Сумма", Новый ОписаниеТипов("Число"));
Ошибки = ОшибкиПараметров(ВходныеПараметры, Схема);
Если Ошибки.Количество() > 0 Тогда
ВызватьИсключение СтрСоединить(Ошибки, Символы.ПС);
КонецЕсли;
Функция собирает все ошибки сразу, а не останавливается на первой. Пользователю или разработчику смежной системы проще исправить данные за один заход.
Универсальный «разборщик» значения
Когда нужно залогировать, что именно пришло, пригодится короткая функция-описатель:
&НаСервере
Функция ОписаниеЗначения(Значение)
Т = ТипЗнч(Значение);
Если Т = Тип("Неопределено") Или Т = Тип("Null") Тогда
Возврат Строка(Т);
КонецЕсли;
ОбъектМД = Метаданные.НайтиПоТипу(Т);
Если ОбъектМД <> Неопределено Тогда
Возврат ОбъектМД.ПолноеИмя() + ": " + Строка(Значение);
КонецЕсли;
Возврат Строка(Т) + ": " + Строка(Значение);
КонецФункции
Для числа она вернёт тип и само число, для ссылки — полное имя объекта метаданных и представление ссылки.
Проверьте себя
Частые вопросы
Чем ТипЗнч отличается от Тип?
ТипЗнч() получает тип у существующего значения. Тип() создаёт дескриптор по имени типа. Первое — «что лежит в переменной», второе — «с чем сравнить».Можно ли проверить тип в тонком клиенте?
ТипЗнч и Тип работают на клиенте. Но многие типы, например СправочникОбъект и ТаблицаЗначений, на клиенте недоступны, поэтому сложные проверки удобнее делать в серверной процедуре.Как получить имя типа строкой для сохранения в базе?
XMLТипЗнч(Значение).ИмяТипа: оно не зависит от синонимов и языка интерфейса, в отличие от Строка(ТипЗнч()).Почему проверка типа работает в одной базе и падает в другой?
Тип("...") указан объект, которого нет в другой конфигурации. Имя метаданных должно существовать в той базе, где выполняется код.Как определить тип значения в отладчике?
ТипЗнч(ИмяПеременной). Колонка типа в окне также покажет тип текущего значения.