Ссылка или объект в переменной: проверка типа значения во встроенном языке 1С

· 9 мин

Содержание

Чтобы определить, объект или ссылка лежит в переменной, сравните тип значения с нужным типом: ТипЗнч(Значение) = Тип("СправочникСсылка.Номенклатура") или ТипЗнч(Значение) = Тип("СправочникОбъект.Номенклатура"). Если конкретный справочник заранее неизвестен, используйте Справочники.ТипВсеСсылки().СодержитТип(ТипЗнч(Значение)) для ссылок и XMLТип(ТипЗнч(Значение)).ИмяТипа для объектов.

Выбор способа зависит от задачи. Точное сравнение с Тип() подходит, когда вы знаете вид и имя метаданных. Проверка по коллекции ТипВсеСсылки() нужна в общих модулях, куда может прийти что угодно. Метаданные по типу (Метаданные.НайтиПоТипу) отвечают на вопрос «из какого справочника или документа это значение». Всё это работает в платформе 8.3 одинаково для любой конфигурации: БП, ЗУП, УТ или самописной.

Ссылка и объект: в чём разница для кода

Ссылка — это указатель на запись в базе. Объект — копия этой записи в памяти, которую можно менять и записывать. Отсюда все практические различия.

ПризнакСсылка (СправочникСсылка.X)Объект (СправочникОбъект.X)
Что хранитИдентификатор записиВсе реквизиты и табличные части
Можно изменитьНетДа, затем Записать()
Где доступнаНа клиенте и сервереТолько на сервере
Чтение реквизита через точкуДа, на сервере, с обращением к базеДа, из памяти
Хранится в реквизите другого объектаДаНет
Как получить другую сторонуСсылка.ПолучитьОбъект()Объект.Ссылка

Несколько следствий, о которых чаще всего спотыкаются:

  • 🔹 Обращение через точку у ссылки — это чтение из базы. Ссылка.Наименование в цикле по 10 000 строк — это до 10 000 обращений к данным. Для массовых чтений берите запрос или ОбщегоНазначения.ЗначенияРеквизитовОбъекта из БСП.
  • 🔹 ПолучитьОбъект() читает запись целиком, со всеми табличными частями. Вызывать его ради одного реквизита — дорого.
  • 🔹 У нового объекта ссылка пустая до первой записи. Объект.Ссылка.Пустая() вернёт Истина, а Объект.ЭтоНовый() — тоже Истина.
  • 🔹 Если запись удалена, ссылка остаётся «битой»: Ссылка.ПолучитьОбъект() вернёт Неопределено, а в форме вы увидите <Объект не найден>.

На клиенте в управляемом приложении объектов нет вовсе. Если код может выполняться и на клиенте, и на сервере, туда может прийти только ссылка или данные формы. Проверка «а вдруг это объект» на клиенте не нужна.

У перечислений объекта нет. Значение перечисления — это всегда ПеречислениеСсылка.ИмяПеречисления. Его сравнивают с элементом напрямую: Значение = Перечисления.СтавкиНДС.НДС20.

Функция ТипЗнч: как узнать тип значения переменной

ТипЗнч() возвращает значение типа Тип. Само по себе оно ничего не говорит: его сравнивают с другим типом, полученным через Тип().

Если ТипЗнч(Значение) = Тип("СправочникСсылка.Контрагенты") Тогда
    // пришла ссылка на контрагента
ИначеЕсли ТипЗнч(Значение) = Тип("ДокументОбъект.РеализацияТоваровУслуг") Тогда
    // пришёл объект документа
ИначеЕсли ТипЗнч(Значение) = Тип("Строка") Тогда
    // пришла строка
КонецЕсли;

Строка в Тип() собирается по шаблону «ВидОбъектаСсылка.Имя» или «ВидОбъектаОбъект.Имя». Основные виды:

Вид метаданныхТип ссылкиТип объекта
СправочникСправочникСсылка.ИмяСправочникОбъект.Имя
ДокументДокументСсылка.ИмяДокументОбъект.Имя
ПеречислениеПеречислениеСсылка.Имя—
План видов характеристикПланВидовХарактеристикСсылка.ИмяПланВидовХарактеристикОбъект.Имя
План счетовПланСчетовСсылка.ИмяПланСчетовОбъект.Имя
План обменаПланОбменаСсылка.ИмяПланОбменаОбъект.Имя
Бизнес-процессБизнесПроцессСсылка.ИмяБизнесПроцессОбъект.Имя
ЗадачаЗадачаСсылка.ИмяЗадачаОбъект.Имя

Для примитивов имена простые: Строка, Число, Дата, Булево, Неопределено, Null. Для универсальных коллекций — Массив, Структура, Соответствие, ТаблицаЗначений.

Если в Тип() указать имя, которого нет в конфигурации, код упадёт с ошибкой «Тип не определен». Это частая причина падений после переименования справочника или при переносе кода между конфигурациями. Опечатка в имени тоже ловится только при выполнении, а не при проверке модуля.

Не сравнивайте типы через строку: Строка(ТипЗнч(Значение)) = "Контрагенты". Строковое представление типа берётся из синонима, а синоним может поменяться при обновлении или зависеть от языка интерфейса.

Если одну и ту же проверку вы делаете в цикле, вынесите Тип() в переменную до цикла. Разбор строки с именем типа на каждой итерации — лишняя работа:

ТипКонтрагент = Тип("СправочникСсылка.Контрагенты");
Для Каждого Строка Из Таблица Цикл
    Если ТипЗнч(Строка.Субконто1) = ТипКонтрагент Тогда
        // ...
    КонецЕсли;
КонецЦикла;

Как определить, ссылка это, объект или перечисление, если имя заранее неизвестно

В общих модулях, подписках на события и обработках конкретное имя справочника заранее не известно. Перебирать все типы вручную бессмысленно. Есть три надёжных приёма.

Приём 1. ТипВсеСсылки() — проверка на любую ссылку

У каждого менеджера коллекции есть метод ТипВсеСсылки(). Он возвращает ОписаниеТипов со всеми ссылочными типами этого вида:

Тип = ТипЗнч(Значение);
ЭтоСсылкаСправочника = Справочники.ТипВсеСсылки().СодержитТип(Тип);
ЭтоСсылкаДокумента   = Документы.ТипВсеСсылки().СодержитТип(Тип);
ЭтоПеречисление      = Перечисления.ТипВсеСсылки().СодержитТип(Тип);

Метод есть у Справочники, Документы, Перечисления, ПланыВидовХарактеристик, ПланыСчетов, ПланыВидовРасчета, ПланыОбмена, БизнесПроцессы, Задачи. Чтобы проверить «это вообще ссылка», объедините проверки через ИЛИ.

Приём 2. XMLТип() — проверка на объект по имени типа

Отдельного «ТипВсехОбъектов» в платформе нет. Зато у объектов есть XML-имя типа, по префиксу которого видно вид:

ТипXML = XMLТип(ТипЗнч(Значение));
Если ТипXML <> Неопределено Тогда
    ИмяТипа = ТипXML.ИмяТипа; // например, "CatalogObject.Номенклатура"
    ЭтоОбъектСправочника = СтрНачинаетсяС(ИмяТипа, "CatalogObject.");
    ЭтоОбъектДокумента   = СтрНачинаетсяС(ИмяТипа, "DocumentObject.");
    ЭтоСсылкаСправочника = СтрНачинаетсяС(ИмяТипа, "CatalogRef.");
КонецЕсли;

Префиксы английские при любом языке конфигурации: CatalogRef, CatalogObject, DocumentRef, DocumentObject, EnumRef, ChartOfAccountsRef, ChartOfCharacteristicTypesRef, ExchangePlanRef и соответствующие ...Object. Для типов, у которых нет XML-представления (например, менеджер справочника), функция вернёт Неопределено — поэтому проверка на Неопределено перед .ИмяТипа обязательна.

Приём 3. Метаданные.НайтиПоТипу() — к какому объекту конфигурации относится значение

Если нужно имя справочника или документа, получите объект метаданных:

ОбъектМетаданных = Метаданные.НайтиПоТипу(ТипЗнч(Значение));
Если ОбъектМетаданных <> Неопределено Тогда
    ПолноеИмя = ОбъектМетаданных.ПолноеИмя(); // "Справочник.Номенклатура"
    ЭтоДокумент = Метаданные.Документы.Содержит(ОбъектМетаданных);
КонецЕсли;

Для ссылки и для объекта одного справочника метод вернёт один и тот же объект метаданных. Поэтому НайтиПоТипу отвечает на вопрос «откуда значение», но не отличает ссылку от объекта. Для этого нужен приём 1 или 2. Для примитивов (Строка, Число) метод вернёт Неопределено.

Глобальный контекст Метаданные на клиенте недоступен. Приёмы 2 и 3 выполняйте на сервере. ТипЗнч() и Тип() работают в любом контексте.

Готовые функции БСП

В типовых конфигурациях на БСП (БП 3.0, ЗУП 3.1, УТ 11) писать эти проверки самому не нужно. В общем модуле ОбщегоНазначения есть:

  • ✅ ОбщегоНазначения.ЭтоСсылка(Тип) — принимает тип, а не значение. Возвращает Истина для любого ссылочного типа.
  • ✅ ОбщегоНазначения.ЗначениеСсылочногоТипа(Значение) — то же, но принимает само значение.
  • ✅ ОбщегоНазначения.ВидОбъектаПоСсылке(Ссылка) — вернёт строку вида «Справочник», «Документ».
  • ✅ ОбщегоНазначения.ЭтоСправочник(ОбъектМетаданных), ЭтоДокумент(...), ЭтоПеречисление(...) — принимают объект метаданных.

Частая ошибка — передать в ЭтоСсылка значение вместо типа. Пишите ОбщегоНазначения.ЭтоСсылка(ТипЗнч(Значение)).

Определение типа в запросе: ССЫЛКА и ТИПЗНАЧЕНИЯ

Когда значение лежит в поле составного типа (регистратор, субконто, «Документ-основание»), проверять тип удобнее прямо в запросе, а не после выгрузки.

  • 🔸 Оператор ССЫЛКА проверяет, что значение — ссылка на объект указанной таблицы:
ВЫБРАТЬ
    Хозрасчетный.Регистратор
ИЗ
    РегистрБухгалтерии.Хозрасчетный КАК Хозрасчетный
ГДЕ
    Хозрасчетный.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг
  • 🔸 Функция ТИПЗНАЧЕНИЯ() и ТИП() нужны, когда тип выводится в результат или участвует в группировке:
ВЫБРАТЬ
    ТИПЗНАЧЕНИЯ(Хозрасчетный.Регистратор) КАК ТипДокумента,
    КОЛИЧЕСТВО(*) КАК Проводок
ИЗ
    РегистрБухгалтерии.Хозрасчетный КАК Хозрасчетный
СГРУППИРОВАТЬ ПО
    ТИПЗНАЧЕНИЯ(Хозрасчетный.Регистратор)

Условие ТИПЗНАЧЕНИЯ(Поле) = ТИП(Документ.РеализацияТоваровУслуг) даёт тот же отбор, что ССЫЛКА. Запись через ССЫЛКА короче и читается легче.

Отдельный плюс ВЫРАЗИТЬ(Поле КАК Документ.РеализацияТоваровУслуг): при обращении к реквизитам поля составного типа через точку платформа соединяет все таблицы из состава типа. Явное приведение оставляет одну таблицу, и запрос работает заметно быстрее.

ОписаниеТипов: составные типы и приведение значений

ОписаниеТипов — это набор допустимых типов. Его возвращает ТипВсеСсылки(), оно же лежит в свойстве Тип любого реквизита в метаданных. Его можно создать и вручную.

// Строка с перечнем типов через запятую
ДопустимыеТипы = Новый ОписаниеТипов("СправочникСсылка.Контрагенты, СправочникСсылка.ФизическиеЛица");

Если ДопустимыеТипы.СодержитТип(ТипЗнч(Значение)) Тогда
    // значение подходит
КонецЕсли;

Основные методы:

МетодЧто делает
СодержитТип(Тип)Проверяет, входит ли тип в описание
Типы()Возвращает массив всех типов описания
ПривестиЗначение(Значение)Приводит значение к описанию; если не подходит — вернёт значение по умолчанию

Как узнать, какие типы допустимы в реквизите составного типа:

ОписаниеРеквизита = Метаданные.Документы.РеализацияТоваровУслуг.Реквизиты.ДокументОснование.Тип;
Для Каждого ДопустимыйТип Из ОписаниеРеквизита.Типы() Цикл
    Сообщить(Метаданные.НайтиПоТипу(ДопустимыйТип).ПолноеИмя());
КонецЦикла;

Имя реквизита ДокументОснование здесь для примера: в вашей конфигурации подставьте реальный реквизит с составным типом. Если в составе есть примитивный тип, НайтиПоТипу вернёт для него Неопределено — в рабочем коде добавьте проверку.

ПривестиЗначение удобно использовать для безопасной загрузки из внешних источников. Новый ОписаниеТипов("Число").ПривестиЗначение("абв") вернёт 0, а не вызовет ошибку.

Неопределено, Null и пустая ссылка: три разных «пусто»

Три значения выглядят одинаково пустыми, но ведут себя по-разному. Перепутать их — классическая ошибка.

ЗначениеОткуда берётсяТипПроверка
НеопределеноПустой реквизит составного типа, несуществующий ключ в Соответствии, необязательный параметрТип("Неопределено")Значение = Неопределено
NullПоле запроса из левого соединения без пары, реквизит, которого нет у данного типаТип("Null")Значение = Null
Пустая ссылкаНезаполненный реквизит ссылочного типаТип ссылки, например СправочникСсылка.СкладыЗначение.Пустая()

Что из этого следует:

  • ⚠️ ТипЗнч(ПустаяСсылка) возвращает тип ссылки, а не Неопределено. Проверка ТипЗнч(Склад) = Тип("СправочникСсылка.Склады") пройдёт и для пустого склада.
  • ⚠️ Вызов .Пустая() у Неопределено или Null даст ошибку «Метод объекта не обнаружен ». Сначала проверьте тип, потом пустоту.
  • ✅ ЗначениеЗаполнено(Значение) возвращает Ложь для всех трёх случаев, а также для 0, пустой строки и пустой даты. Для вопроса «есть ли значение» этого достаточно.

Если реквизит составного типа, сначала проверяйте тип, потом обращайтесь к реквизитам значения. Иначе код упадёт на первой же строке, где в поле лежит строка или число.

В запросе Null обрабатывают функцией ЕСТЬNULL(Поле, ЗначениеПоУмолчанию) или условием Поле ЕСТЬ NULL. Сравнение Поле = NULL в языке запросов не работает так, как ожидается.

Практические приёмы: функции на каждый день

Получить ссылку, что бы ни пришло

В общую процедуру может прийти и ссылка, и объект (например, из подписки на событие ПередЗаписью приходит объект, а из формы списка — ссылка). Функция ниже возвращает ссылку в обоих случаях. Выполнять на сервере:

Функция СсылкаПоЗначению(ОбъектИлиСсылка) Экспорт
    Тип = ТипЗнч(ОбъектИлиСсылка);
    Если ОбщегоНазначения.ЭтоСсылка(Тип) Тогда
        Возврат ОбъектИлиСсылка;
    КонецЕсли;
    ТипXML = XMLТип(Тип);
    Если ТипXML <> Неопределено
        И СтрЗаканчиваетсяНа(СтрРазделить(ТипXML.ИмяТипа, ".")[0], "Object") Тогда
        Возврат ОбъектИлиСсылка.Ссылка;
    КонецЕсли;
    Возврат Неопределено;
КонецФункции

Если БСП в конфигурации нет, замените первую проверку на объединение ТипВсеСсылки() из приёма 1. Для нового незаписанного объекта функция вернёт пустую ссылку — учитывайте это в вызывающем коде.

Обработать поле составного типа без ошибок

Если ЗначениеЗаполнено(Основание)
    И ТипЗнч(Основание) = Тип("ДокументСсылка.ЗаказКлиента") Тогда
    Дата = Основание.Дата;
КонецЕсли;

Порядок условий важен: в 1С операторы И и ИЛИ вычисляются сокращённо. Если первое условие ложно, второе не проверяется.

Короткие правила

  • 📌 Тип сравнивайте с Тип(), а не со строкой.
  • 📌 Тип() с конкретным именем — для известных объектов, ТипВсеСсылки() — для любых.
  • 📌 Объект не таскайте между клиентом и сервером — передавайте ссылку.
  • 📌 Объект ради одного реквизита не получайте: для этого есть запрос или чтение реквизитов через БСП.
Проверьте себя
Что вернёт ТипЗнч() для незаполненного реквизита типа СправочникСсылка.Склады?
Какой оператор языка запросов отбирает регистраторы одного вида документа?
Что вернёт Метаданные.НайтиПоТипу() для типа Строка?

Частые вопросы

Почему ТипЗнч() на клиенте не даёт отличить объект от ссылки?
На клиенте управляемого приложения объектов справочников и документов нет. В форме работают с данными формы, а на клиент передаётся только ссылка.
Чем отличается Объект.ЭтоНовый() от Объект.Ссылка.Пустая()?
Для нового незаписанного объекта обе проверки дают Истина. ЭтоНовый() есть только у объекта, а Пустая() — у любой ссылки, поэтому её используют там, где объекта нет: в форме списка, в параметрах процедур, на клиенте.
Как узнать тип элементов в массиве, если они разные?
Обойдите массив циклом и проверяйте ТипЗнч() каждого элемента. Общего типа у массива нет, он хранит значения любых типов.
Можно ли проверить тип в отладчике?
Да. В окне вычисления выражения или в табло отладчика рядом со значением показан его тип, например «СправочникСсылка.Номенклатура».
Почему код с Тип("ДокументСсылка...") работает в одной базе и падает в другой?
Во второй конфигурации нет документа с таким именем. Тип() ищет имя в метаданных при выполнении и выдаёт ошибку «Тип не определен».