При переходе с одной версии платформы на другую или при рефакторинге старого кода разработчики часто сталкиваются с ситуациями, когда логика, которая, казалось бы, работала годами, внезапно перестает выполняться. Одна из таких классических проблем связана с итерацией коллекций и проверкой типов данных внутри цикла.
В этой статье мы подробно разберем ситуацию, когда цикл по списку перечислений проходит успешно, но условия внутри него (Если...Тогда) не срабатывают. Мы проанализируем структуру объектов платформы, разберем различия между списком значений и массивом, а также научимся правильно отлаживать подобные ошибки.
Рассмотрим конкретный пример кода, который вызывает вопросы. Представим, что у нас есть задача создать документы «Ввод начальных остатков» для разных видов операций (расчеты с покупателями и поставщиками). Код выглядит логично и структурировано:
СписокВидовОпераций = Новый СписокЗначений;
СписокВидовОпераций.Добавить(Перечисления.ВидыОпераций.РасчетыСПокупателями);
СписокВидовОпераций.Добавить(Перечисления.ВидыОпераций.РасчетыСПоставщиками);
Для Каждого ВидОперации Из СписокВидовОпераций Цикл
ДокВвода = Документы.ВводНачальныхОстатков.СоздатьДокумент();
ДокВвода.ВидОперации = ВидОперации;
// Проблемное место: условие ниже не выполняется
Если ДокВвода.ВидОперации = Перечисления.ВидыОпераций.РасчетыСПокупателями Тогда
// Какой-то код
КонецЕсли;
КонецЦикла;
На первый взгляд все верно: мы добавляем перечисления в список, затем перебираем их и сравниваем. Однако при отладке выясняется, что даже когда очередь доходит до «Расчетов с покупателями», программа не заходит внутрь условия Если. Почему так происходит? Давайте разбираться детально.
Главная ошибка кроется в понимании того, что именно попадает в переменную итератора (в нашем случае переменную ВидОперации) при обходе коллекции СписокЗначений.
Платформа 1С:Предприятие имеет строгую типизацию объектов. Давайте посмотрим, чем отличаются коллекции Массив и СписокЗначений.
Когда мы выполняем метод СписокВидовОпераций.Добавить(Значение), платформа создает специальный объект-обертку — ЭлементСпискаЗначений. Этот объект содержит несколько свойств:
Значение — то, что мы туда положили (наша ссылка на перечисление).Представление — строковое описание для пользователя.Пометка — булево (галочка).Картинка — визуальное оформление.Теперь проанализируем наш цикл:
Для Каждого ВидОперации Из СписокВидовОпераций Цикл
// В этот момент переменная ВидОперации имеет тип "ЭлементСпискаЗначений"
// А НЕ "ПеречислениеСсылка"
// Здесь происходит неявное присваивание, или запись объекта-обертки,
// что может привести к ошибке типов или некорректному заполнению
ДокВвода.ВидОперации = ВидОперации;
КонецЦикла;
Когда выполняется строка Если ДокВвода.ВидОперации = Перечисления..., происходит сравнение двух разных сущностей:
ЭлементСпискаЗначений, если реквизит составного типа или типа Произвольный).ВидОперации: Объект ЭлементСпискаЗначений.Если в коде написано ДокВвода.ВидОперации = ВидОперации, а реквизит документа имеет жесткий тип ПеречислениеСсылка, платформа в некоторых случаях может выдать ошибку несоответствия типов сразу. Но если мы сравниваем переменную цикла напрямую:
// Это сравнение ВСЕГДА будет Ложь
Если ВидОперации = Перечисления.ВидыОпераций.РасчетыСПокупателями Тогда
Оно ложно, потому что Элемент списка значений (обертка) не равен Значению (содержимому), которое лежит внутри него.
Разберем два правильных способа исправить этот код.
Если вам необходимо использовать именно СписокЗначений (например, он приходит с формы или используется для отбора), то при обращении к элементу цикла нужно явно указывать свойство .Значение.
Для Каждого ЭлементСписка Из СписокВидовОпераций Цикл
// Получаем реальное значение перечисления из обертки
РеальныйВидОперации = ЭлементСписка.Значение;
ДокВвода = Документы.ВводНачальныхОстатков.СоздатьДокумент();
ДокВвода.ВидОперации = РеальныйВидОперации;
// Теперь сравниваем Ссылку со Ссылкой - условие сработает
Если РеальныйВидОперации = Перечисления.ВидыОпераций.РасчетыСПокупателями Тогда
// Код выполняется
КонецЕсли;
КонецЦикла;
Если коллекция создается исключительно программно для алгоритма и не показывается пользователю в интерфейсе, использование СпискаЗначений избыточно. Он потребляет больше ресурсов памяти из-за создания объектов-оберток. Правильнее использовать Массив.
// Используем Массив вместо СпискаЗначений
МассивВидов = Новый Массив;
МассивВидов.Добавить(Перечисления.ВидыОпераций.РасчетыСПокупателями);
МассивВидов.Добавить(Перечисления.ВидыОпераций.РасчетыСПоставщиками);
Для Каждого ВидОперации Из МассивВидов Цикл
// При итерации массива переменная сразу содержит ЗНАЧЕНИЕ (ПеречислениеСсылка)
ДокВвода = Документы.ВводНачальныхОстатков.СоздатьДокумент();
ДокВвода.ВидОперации = ВидОперации;
// Условие работает корректно без дополнительных свойств
Если ВидОперации = Перечисления.ВидыОпераций.РасчетыСПокупателями Тогда
// Код выполняется
КонецЕсли;
КонецЦикла;
Часто можно услышать: "Но в 8.2 это работало!". Давайте проанализируем это утверждение. Объектная модель СпискаЗначений не менялась кардинально между версиями 8.2 и 8.3 в плане структуры элемента. Скорее всего, имеют место следующие факторы:
Массив, а при переписывании случайно заменили на СписокЗначений.Получить(i)), где могло происходить обращение к значению.В любом случае, полагаться на неявное поведение нельзя. Код должен быть прозрачным и типизированным.
Рассмотрим еще один вопрос, возникший в ходе обсуждения: почему отладчик игнорирует цикл Для Каждого Стр Из ТаблицаЗначений? Курсор отладчика просто "перепрыгивает" тело цикла, не заходя внутрь.
Существует три основные причины такого поведения. Проанализируем их от простого к сложному.
Самая банальная и частая причина — в таблице значений просто нет строк. Визуально или логически вам может казаться, что данные там должны быть, но на момент выполнения цикла таблица пуста.
Как проверить:
Перед циклом поставьте точку останова и используйте "Вычислить выражение" (Shift+F9) для команды:
Результат.Количество()
Если вернется 0, значит, проблема в заполнении таблицы или запросе, а не в цикле.
Иногда переменная называется Таблица, но по факту содержит тип РезультатЗапроса. У этого объекта другая логика обхода.
Если вы используете конструкцию Для Каждого для результата запроса, она сработает, но это не самый оптимальный путь (лучше использовать Выборка = Результат.Выбрать(); Пока Выборка.Следующий()...). Однако, если результат запроса был предварительно выгружен (метод Выгрузить()), то мы работаем с таблицей.
Это критически важный момент для управляемого приложения (версия 8.3). Тип ТаблицаЗначений доступен только на сервере.
ТаблицейЗначений, а ДаннымиФормыКоллекция (это данные табличной части на форме).Если код выполняется на клиенте, и вы пытаетесь работать с объектом, который там недоступен или пуст из-за особенностей контекста, цикл может не выполниться.
Чтобы в будущем избегать подобных ситуаций и не гадать "почему код не работает", выработайте привычку проверять типы данных прямо в отладчике.
ВидОперации).Если вы увидите тип ЭлементСпискаЗначений, а ожидаете ПеречислениеСсылка, вы сразу поймете, что нужно обратиться к свойству .Значение.
Также полезна функция ТипЗнч(Переменная). Вы можете написать в табло отладчика:
ТипЗнч(ВидОперации)
Это вернет точный тип объекта, с которым вы работаете в данный момент.
Работа с коллекциями в 1С требует внимания к деталям реализации объектов. СписокЗначений — это "обертка" для удобства интерфейса, а Массив — контейнер для данных. Понимание этой разницы и использование отладчика для проверки типов гарантирует, что ваши условия и циклы будут работать корректно на любой версии платформы.