Значения перечислений во встроенном языке и запросах 1С 8.3: как проверять их без ошибок
Содержание
Значение перечисления в 1С сравнивают со ссылкой на нужное значение через обычное =: Если ВидОперации = Перечисления.ВидыОпераций.Поступление Тогда. В запросе для того же служит конструкция ЗНАЧЕНИЕ(Перечисление.ВидыОпераций.Поступление), а в клиентском коде управляемой формы — функция ПредопределенноеЗначение("Перечисление.ВидыОпераций.Поступление").
Сравнение со строкой вида ВидОперации = "Поступление" синтаксически допустимо, но всегда вернёт Ложь: слева ссылка, справа строка. Ошибки нет, условие просто никогда не срабатывает. Так проявляется большинство ошибок с перечислениями: код выглядит правильным, а ветка не выполняется.
Что такое значение перечисления с точки зрения платформы
Перечисление — объект метаданных, набор значений которого задаётся в конфигураторе. В режиме 1С:Предприятие добавить или удалить значение нельзя: список фиксирован до следующего изменения конфигурации.
Отсюда несколько свойств, которые определяют все способы сравнения:
- 🔗 Значение — это ссылка. Тип реквизита —
ПеречислениеСсылка.ИмяПеречисления. В базе хранится не имя и не синоним, а внутренний идентификатор значения. - 🚫 Объекта у перечисления нет. Типа
ПеречислениеОбъектне существует: значение нельзя «открыть», изменить или записать. Работают только ссылка и менеджерПеречисления.ИмяПеречисления. - 📋 У каждого значения есть имя, синоним и порядковый номер (индекс). Имя — технический идентификатор (
Поступление), синоним — то, что видит пользователь («Поступление товаров»), индекс — позиция в списке значений в конфигураторе, начиная с нуля. - ⭕ Есть пустое значение. Реквизит типа перечисления, который не заполнили, содержит пустую ссылку. Её получают через
Перечисления.ИмяПеречисления.ПустаяСсылка().
| Что | Как получить | Можно ли на это опираться при сравнении |
|---|---|---|
| Ссылка | Перечисления.ВидыОпераций.Поступление | Да, основной способ |
| Имя значения | XMLСтрока(Значение) | Да, если имя пришло извне (файл, обмен) |
| Индекс | Перечисления.ВидыОпераций.Индекс(Значение) | Только для сравнения порядка |
| Синоним | Строка(Значение) | Нет, только для вывода пользователю |
Сравнение значения перечисления со ссылкой в коде
Базовый вариант — сравнение двух ссылок. Работают операторы = и <>:
Если Документ.ВидОперации = Перечисления.ВидыОпераций.Поступление Тогда
ОбработатьПоступление(Документ);
ИначеЕсли Документ.ВидОперации <> Перечисления.ВидыОпераций.Возврат Тогда
ОбработатьПрочее(Документ);
КонецЕсли;
Если имя значения записано с опечаткой, ошибка возникнет только при выполнении этой строки: Поле объекта не обнаружено (Поступлние). Синтаксический контроль модуля её не поймает, поэтому ветки с редкими значениями стоит хотя бы раз прогнать отладчиком.
Проверка на пустое значение
Есть три равноценных по результату способа:
// 1. Сравнение с пустой ссылкой
Если ВидОперации = Перечисления.ВидыОпераций.ПустаяСсылка() Тогда
// 2. Метод самой ссылки
Если ВидОперации.Пустая() Тогда
// 3. Универсальная функция
Если Не ЗначениеЗаполнено(ВидОперации) Тогда
Третий вариант удобнее, когда тип переменной заранее не известен: ЗначениеЗаполнено() вернёт Ложь и для пустой ссылки, и для Неопределено. Метод Пустая() на значении Неопределено вызовет ошибку — это бывает, если реквизит составного типа или переменную ещё не присвоили.
Проверка, что значение входит в набор
Длинная цепочка Или читается плохо. Вместо неё удобно собрать массив и проверить вхождение:
Допустимые = Новый Массив;
Допустимые.Добавить(Перечисления.ВидыОпераций.Поступление);
Допустимые.Добавить(Перечисления.ВидыОпераций.ПоступлениеИзПереработки);
Если Допустимые.Найти(ВидОперации) <> Неопределено Тогда
// значение входит в набор
КонецЕсли;
Проверка типа
Если реквизит составного типа и в нём может лежать значение одного из нескольких перечислений, сначала проверьте тип:
Если ТипЗнч(Значение) = Тип("ПеречислениеСсылка.ВидыОпераций") Тогда
Сравнение по имени и индексу значения перечисления
Иногда на входе не ссылка, а строка: имя значения из XML, JSON, настроек или параметра внешней обработки. Тогда есть два пути.
Получить ссылку по имени. Менеджер перечисления поддерживает обращение через квадратные скобки:
ИмяИзФайла = "Поступление";
Значение = Перечисления.ВидыОпераций[ИмяИзФайла];
Если такого имени нет, будет ошибка выполнения. Для данных извне оберните вызов в Попытка … Исключение или проверьте имя по метаданным:
Если Метаданные.Перечисления.ВидыОпераций.ЗначенияПеречисления.Найти(ИмяИзФайла) <> Неопределено Тогда
Значение = Перечисления.ВидыОпераций[ИмяИзФайла];
КонецЕсли;
Получить имя по ссылке. Для перечислений XMLСтрока() возвращает именно имя значения, а для пустой ссылки — пустую строку:
Если XMLСтрока(ВидОперации) = "Поступление" Тогда
Обратное преобразование — XMLЗначение(Тип("ПеречислениеСсылка.ВидыОпераций"), "Поступление").
Второй способ получить имя — через индекс и метаданные:
Индекс = Перечисления.ВидыОпераций.Индекс(ВидОперации);
Имя = Метаданные.Перечисления.ВидыОпераций.ЗначенияПеречисления[Индекс].Имя;
Для пустой ссылки Индекс() вернёт -1, поэтому перед обращением к коллекции по индексу проверяйте заполненность.
Сравнение по порядку
Операторы < и > для ссылок не работают: платформа выдаст ошибку о том, что сравнение на больше-меньше допустимо только для примитивных типов. Если логика зависит от порядка значений (например, «статус не ниже Согласован»), сравнивайте индексы:
Менеджер = Перечисления.СтатусыЗаявок;
Если Менеджер.Индекс(Статус) >= Менеджер.Индекс(Менеджер.Согласована) Тогда
Индекс зависит от позиции значения в конфигураторе. Если разработчик перетащит значение выше или ниже, логика «по порядку» сломается без единой ошибки. Используйте этот приём, только если порядок значений — часть смысла перечисления и это записано в комментарии к нему.
Сравнение перечислений в запросе
В тексте запроса нельзя обращаться к менеджеру Перечисления. Есть два рабочих варианта.
Конструкция ЗНАЧЕНИЕ() — для фиксированного значения:
ВЫБРАТЬ
Документ.Ссылка
ИЗ
Документ.ПоступлениеТоваров КАК Документ
ГДЕ
Документ.ВидОперации = ЗНАЧЕНИЕ(Перечисление.ВидыОпераций.Поступление)
Обратите внимание: в запросе пишется Перечисление в единственном числе, а в коде — Перечисления. Пустое значение — ЗНАЧЕНИЕ(Перечисление.ВидыОпераций.ПустаяСсылка), без скобок после ПустаяСсылка.
Параметр запроса — когда значение или список вычисляются в коде:
ГДЕ
Документ.ВидОперации В (&ВидыОпераций)
Запрос.УстановитьПараметр("ВидыОпераций", Допустимые); // массив ссылок
NULL в соединениях
После левого соединения поле перечисления может прийти как NULL. Условие Поле = ЗНАЧЕНИЕ(...) для NULL ложно, Поле <> ЗНАЧЕНИЕ(...) — тоже ложно. Строки «без значения» тихо выпадают из выборки. Если их нужно учитывать, оберните поле:
ГДЕ
ЕСТЬNULL(Статусы.Статус, ЗНАЧЕНИЕ(Перечисление.СтатусыЗаявок.ПустаяСсылка))
<> ЗНАЧЕНИЕ(Перечисление.СтатусыЗаявок.Закрыта)
Ветвление внутри запроса
Для вычисляемых колонок используйте ВЫБОР:
ВЫБОР
КОГДА Документ.ВидОперации = ЗНАЧЕНИЕ(Перечисление.ВидыОпераций.Возврат)
ТОГДА -Документ.Сумма
ИНАЧЕ Документ.Сумма
КОНЕЦ КАК СуммаСоЗнаком
Проверка типа в поле составного типа — оператор ССЫЛКА: ГДЕ Регистр.Аналитика ССЫЛКА Перечисление.ВидыОпераций.
Упорядочивание УПОРЯДОЧИТЬ ПО Документ.ВидОперации сортирует значения в том порядке, в котором они стоят в конфигураторе, а не по алфавиту синонимов.
Почему нельзя сравнивать по строковому представлению
Соблазн понятен: Если Строка(ВидОперации) = "Поступление товаров" Тогда читается как обычный текст. Но так делать не стоит:
- ✏️ Синоним меняется. Разработчик поправит синоним на «Поступление товаров и услуг» — условие перестанет срабатывать, и об этом никто не узнает.
- 🌐 Синоним зависит от языка. В конфигурации с несколькими языками интерфейса
Строка()вернёт текст на языке текущего пользователя. - 🐢 Это медленнее. Получение представления — лишняя операция по сравнению со сравнением двух ссылок.
- 🔍 В запросе ещё хуже. Условие на
ПРЕДСТАВЛЕНИЕ(Документ.ВидОперации)не позволяет СУБД использовать индекс по полю, и отбор превращается в перебор.
Строковое представление нужно для вывода: в сообщения, печатные формы, логи. Если в коде требуется строковое сравнение (данные пришли текстом), сравнивайте по имени через XMLСтрока(), а не по синониму.
| Способ | Устойчив к правке синонима | Работает в запросе | Когда применять |
|---|---|---|---|
Ссылка = | Да | Через ЗНАЧЕНИЕ() | Всегда по умолчанию |
Имя XMLСтрока() | Да | Нет | Обмен, файлы, настройки |
| Индекс | Да, но ломается при перестановке | Нет | Логика «по порядку» |
Синоним Строка() | Нет | Через ПРЕДСТАВЛЕНИЕ(), медленно | Только вывод |
Сравнение перечислений на клиенте управляемой формы
В тонком и веб-клиенте менеджер Перечисления недоступен. Код вида
&НаКлиенте
Процедура ВидОперацииПриИзменении(Элемент)
Если Объект.ВидОперации = Перечисления.ВидыОпераций.Возврат Тогда
упадёт с ошибкой Переменная не определена (Перечисления). На клиенте используйте ПредопределенноеЗначение():
&НаКлиенте
Процедура ВидОперацииПриИзменении(Элемент)
Если Объект.ВидОперации = ПредопределенноеЗначение("Перечисление.ВидыОпераций.Возврат") Тогда
Элементы.ОснованиеВозврата.Видимость = Истина;
КонецЕсли;
КонецПроцедуры
Строка в параметре пишется, как в запросе: Перечисление в единственном числе. Опечатка в имени тоже даст ошибку только при выполнении.
💡 Если одно и то же значение проверяется в нескольких клиентских обработчиках формы, получите его один раз в ПриСозданииНаСервере, положите в реквизит формы или в параметр и используйте дальше. Так и код короче, и серверный вызов не нужен.
Условное оформление и видимость элементов, зависящие от значения перечисления, удобнее настраивать без кода: в условном оформлении формы отбор по реквизиту задаётся выбором значения из списка.
Ошибки при сравнении перечислений и как их найти
Перечисления.Имя.Значение, а не текст. В отладчике посмотрите тип обеих частей через вычисление выражения ТипЗнч().ПредопределенноеЗначение("Перечисление.Имя.Значение") или перенесите проверку в серверную процедуру.ЗНАЧЕНИЕ(): единственное число Перечисление, ПустаяСсылка без скобок.ЗначениеЗаполнено().< и >. Сравнивайте индексы через Перечисления.Имя.Индекс().Ещё одна тихая ошибка — сравнение значений разных перечислений с одинаковыми именами. Перечисления.СтатусыЗаказов.Закрыт и Перечисления.СтатусыЗаявок.Закрыт — разные ссылки, и равенство между ними всегда ложно. Встречается при копировании кода из соседнего модуля.
Как писать сравнения перечислений быстрее и чище
Само сравнение двух ссылок дешёвое. Время уходит не на него, а на то, что происходит вокруг.
1. Выносите значение из цикла. Каждое обращение Перечисления.ВидыОпераций.Поступление — это обращение к менеджеру. В цикле по тысячам строк получите ссылку один раз до цикла:
Поступление = Перечисления.ВидыОпераций.Поступление;
Для Каждого Строка Из ТаблицаДанных Цикл
Если Строка.ВидОперации = Поступление Тогда
// ...
КонецЕсли;
КонецЦикла;
2. Фильтруйте в запросе, а не в коде. Если нужны только документы с одним видом операции, отберите их условием ГДЕ, а не выбирайте всё подряд с последующей проверкой в цикле.
3. Заменяйте длинные ИначеЕсли соответствием. Когда каждому значению перечисления отвечает своё действие или своя настройка, ключом соответствия может быть сама ссылка:
СчетаПоВидам = Новый Соответствие;
СчетаПоВидам.Вставить(Перечисления.ВидыОпераций.Поступление, "60.01");
СчетаПоВидам.Вставить(Перечисления.ВидыОпераций.Возврат, "62.01");
КодСчета = СчетаПоВидам.Получить(ВидОперации);
Если КодСчета = Неопределено Тогда
ВызватьИсключение "Не задан счёт для вида операции " + Строка(ВидОперации);
КонецЕсли;
Новое значение перечисления в такой схеме не проваливается молча в ветку Иначе: код явно сообщит, что для него нет настройки.
4. Не проверяйте одно и то же дважды. Если в начале процедуры уже выяснили, что значение заполнено, повторная проверка ЗначениеЗаполнено() перед каждым сравнением не нужна.
5. Документируйте порядок. Если логика опирается на индексы, напишите об этом в комментарии к перечислению в конфигураторе. Следующий разработчик, который захочет переставить значения «для красоты», увидит предупреждение.
Частые вопросы о сравнении перечислений
Можно ли сравнить значение перечисления с числом?
Перечисления.Имя.Индекс(Значение) и сравнивайте его.Что вернёт XMLСтрока для пустого значения перечисления?
XMLСтрока(Значение) = "" тоже работает как проверка на пустоту, но ЗначениеЗаполнено() понятнее.Как сравнить перечисление во внешней обработке, если его нет в конфигурации?
Перечисления.Имя к несуществующему перечислению вызовет ошибку. Сначала проверьте Метаданные.Перечисления.Найти("Имя") <> Неопределено, а потом обращайтесь к менеджеру через квадратные скобки: Перечисления["Имя"]["Значение"].