Проверка типа переменной, коллекции и ссылки во встроенном языке 1С:Предприятия 8.3

· 9 мин

Содержание

Тип значения переменной в 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ТипЗнч(Значение).ИмяТипа: оно не зависит от синонимов и языка интерфейса, в отличие от Строка(ТипЗнч()).
Почему проверка типа работает в одной базе и падает в другой?
Скорее всего, в Тип("...") указан объект, которого нет в другой конфигурации. Имя метаданных должно существовать в той базе, где выполняется код.
Как определить тип значения в отладчике?
Поставьте точку останова, откройте окно вычисления выражения и введите ТипЗнч(ИмяПеременной). Колонка типа в окне также покажет тип текущего значения.