К статье
Практический материал

Разработка и Конфигурация · 11 мин

1С: как обойти список значений

Как обойти список значений в 1С: циклы Для Каждого и Для, обращение к элементам по индексу, поиск и удаление. Примеры кода, типичные ошибки и FAQ.

Список значений в 1С — это универсальная коллекция, в которой каждый элемент хранит четыре свойства: само значение, представление, пометку (флажок) и картинку. Обход такого списка требуется постоянно: при обработке выбранных пользователем строк, при формировании параметров запроса, при переборе помеченных элементов формы. Встроенный язык даёт несколько способов перебора, и у каждого есть свои ограничения.

Основные варианты обхода — цикл Для Каждого … Из … Цикл и классический цикл Для … По … Цикл с обращением по индексу. Первый удобен для чтения, второй незаменим, когда нужен номер элемента или требуется модифицировать список «на месте» с учётом позиций. Отдельный сценарий — перебор с удалением элементов: здесь прямой цикл опасен, и список обходят в обратном порядке.

Ниже разобраны все рабочие способы с примерами кода, приёмы поиска и удаления, а также ошибки, которые чаще всего приводят к исключениям «Индекс находится за границами массива» и к пропуску элементов.

Что представляет собой список значений и как к нему обращаться

Объект типа СписокЗначений создаётся конструктором Новый СписокЗначений либо приходит готовым — например, как реквизит формы или результат метода ВыгрузитьЗначения() таблицы значений. Каждый элемент списка — это объект со свойствами Значение, Представление, Пометка и Картинка.

Количество элементов возвращает метод Количество(). Обращение к конкретному элементу возможно по индексу через квадратные скобки или метод Получить(Индекс). Важно помнить: нумерация элементов начинается с нуля, то есть первый элемент — Список[0], а последний — Список[Список.Количество() - 1].

Попытка обратиться к несуществующему индексу вызывает исключение, поэтому перед обращением по индексу всегда проверяйте, что список не пуст, а индекс находится в допустимых границах.

Обход списка циклом «Для Каждого»

Цикл Для Каждого — рекомендуемый способ, когда нужно просто прочитать все элементы. Он короче, читаемее и исключает ошибки с индексами:

Для Каждого Элемент Из СписокЗначений Цикл

Сообщить(Элемент.Значение);

КонецЦикла;

Внутри цикла переменная Элемент содержит ссылку на текущий элемент списка, поэтому доступны все его свойства. Типовой сценарий — обработать только помеченные строки:

Для Каждого Элемент Из СписокЗначений Цикл

Если Элемент.Пометка Тогда

ОбработатьДокумент(Элемент.Значение);

КонецЕсли;

КонецЦикла;

У цикла «Для Каждого» есть ограничение: внутри него нельзя удалять или добавлять элементы в обходимый список — это приводит к непредсказуемому поведению или ошибке. Для таких задач используйте индексный цикл.

Обход по индексу циклом «Для»

Когда нужен номер элемента — например, для вывода нумерованного списка или синхронного обхода двух списков, — применяется классический цикл со счётчиком:

Для Индекс = 0 По СписокЗначений.Количество() - 1 Цикл

Элемент = СписокЗначений[Индекс];

Сообщить(Строка(Индекс + 1) + ". " + Элемент.Представление);

КонецЦикла;

Обратите внимание на верхнюю границу: Количество() - 1. Это самая частая ошибка новичков — написать По СписокЗначений.Количество() и получить исключение на последней итерации, потому что индексация идёт с нуля.

Также допустим вариант с методом Получить(): Элемент = СписокЗначений.Получить(Индекс); — по смыслу он эквивалентен обращению через квадратные скобки.

bsl
Для Индекс = 0 По СписокЗначений.Количество() - 1 Цикл
    Элемент = СписокЗначений[Индекс];
    // работа с Элемент.Значение, Элемент.Пометка, Элемент.Представление
КонецЦикла;

Обход с удалением элементов: только с конца

Если удалять элементы в прямом цикле, индексы оставшихся элементов сместятся, и часть строк будет пропущена. Правильный приём — обход от последнего элемента к первому:

Для Индекс = 1 - СписокЗначений.Количество() По 0 Цикл

РеальныйИндекс = -Индекс;

Если Не СписокЗначений[РеальныйИндекс].Пометка Тогда

СписокЗначений.Удалить(РеальныйИндекс);

КонецЕсли;

КонецЦикла;

Альтернатива — цикл Пока с ручным управлением индексом: не увеличивать счётчик после удаления, потому что на место удалённого элемента встал следующий:

Индекс = 0;

Пока Индекс < СписокЗначений.Количество() Цикл

Если Не СписокЗначений[Индекс].Пометка Тогда

СписокЗначений.Удалить(Индекс);

Иначе

Индекс = Индекс + 1;

КонецЕсли;

КонецЦикла;

Третий безопасный вариант — не удалять, а собрать новый список: создать второй СписокЗначений и переложить в него нужные элементы обычным циклом «Для Каждого». Это чуть дороже по памяти, но код проще и не содержит риска смещения индексов.

Безопасное удаление из списка
  1. 1
    Определите условие удаления (например, Элемент.Пометка = Ложь)
  2. 2
    Организуйте обход от конца списка к началу или цикл Пока с ручным индексом
  3. 3
    Вызывайте СписокЗначений.Удалить(Индекс) только для текущего элемента
  4. 4
    После цикла проверьте СписокЗначений.Количество() — оно должно соответствовать ожиданию

Поиск элемента вместо ручного перебора

Часто полный обход вообще не нужен. Если задача — проверить наличие значения, используйте метод НайтиПоЗначению(Значение). Он возвращает найденный элемент или Неопределено:

Найденный = СписокЗначений.НайтиПоЗначению(ИскомаяСсылка);

Если Найденный <> Неопределено Тогда

Найденный.Пометка = Истина;

КонецЕсли;

Для поиска по представлению существует метод НайтиПоПредставлению(Представление), но он возвращает массив всех подходящих элементов, и его результат нужно проверять на пустоту. Поиск по представлению менее надёжен, так как представления могут совпадать у разных значений.

Метод Индекс(Элемент) решает обратную задачу — возвращает позицию известного элемента. Учтите, что все эти методы выполняют линейный поиск: на очень больших списках (десятки тысяч элементов и более) многократный поиск в цикле заметно тормозит. В таких случаях быстрее выгрузить данные в соответствие или таблицу значений с индексом.

Способы обхода и поиска

Для Каждого

Чтение и изменение свойств элементов. Не подходит для удаления и когда нужен индекс.

Для по индексу

Нужен номер элемента или синхронный обход нескольких списков. Требует аккуратности с границей Количество()-1.

Обход с конца / Пока

Удаление или вставка элементов во время обхода. Код сложнее, но безопасен.

НайтиПоЗначению

Разовая проверка наличия значения. Не подходит для поиска по пометке или частичному совпадению.

Обход списка значений на форме и в запросах

Когда список значений является реквизитом формы (например, поле с флажками для выбора нескольких контрагентов), обход выполняется теми же циклами, но помните про контекст: на клиенте недоступны серверные вызовы без обёртки, поэтому тяжёлую обработку результата выносят в серверную процедуру, передавая туда сам список — он сериализуется между клиентом и сервером.

Типовой приём — передать список значений как параметр запроса с оператором В (&Список). Перед этим список полезно «очистить» от непомеченных элементов обходом с конца и от дублей методом СортироватьПоЗначению() с последующим удалением повторов, чтобы запрос работал с компактным набором значений.

Ещё одна частая задача — обход списка, полученного из ТаблицаЗначений.ВыгрузитьКолонку() и последующего ЗагрузитьЗначения(). Здесь помните, что в список могут попасть пустые ссылки и Неопределено, поэтому внутри цикла добавляйте проверку ЗначениеЗаполнено(Элемент.Значение).

Типичные ошибки и как их проверить

Первая ошибка — выход за границу: цикл Для Индекс = 0 По СписокЗначений.Количество() без вычитания единицы. Признак — исключение «Индекс находится за границами массива» на последней итерации, причём на пустом списке ошибка может не проявиться, и дефект уйдёт в рабочую базу.

Вторая ошибка — пропуск элементов при удалении в прямом цикле. Внешне всё «работает», но часть строк остаётся необработанной. Контроль простой: после обхода сравните итоговое Количество() с ожидаемым числом оставшихся элементов.

Третья ошибка — сравнение значений разных типов при поиске. НайтиПоЗначению сравнивает строго: строка «000001» не равна ссылке с таким кодом. Если поиск «не находит» заведомо существующее значение, проверьте типы через ТипЗнч() у искомого значения и элементов списка.

Типичные ошибки
01
Цикл «По Количество()» без минус единицы — исключение на последней итерации
02
Удаление элементов в прямом обходе — пропуск соседних строк из-за смещения индексов
03
Поиск значения другого типа через НайтиПоЗначению — всегда возвращает Неопределено
04
Изменение состава списка внутри «Для Каждого» — непредсказуемый результат
05
Обращение к Список[0] без проверки, что список не пуст

Проверить корректность обхода помогает простая диагностика: выведите в цикле индекс и значение через Сообщить() или используйте отладчик с точкой останова внутри цикла, наблюдая за переменной счётчика и содержимым элемента. Для списков на форме удобно смотреть текущее состояние через табло в отладчике.

Итог

Что важно запомнить

Для чтения используйте «Для Каждого», для работы с индексами — «Для» от 0 до Количество()-1, для удаления — обход с конца или цикл «Пока». Для разового поиска применяйте НайтиПоЗначению вместо ручного перебора и всегда проверяйте типы сравниваемых значений.

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

Можно ли обойти список значений запросом?

Напрямую нет — запрос работает с таблицами. Но список можно передать как параметр оператора В (&Список) для отбора, либо сначала преобразовать его в таблицу значений через построчное заполнение и использовать в пакетном запросе как временную таблицу.

Как проверить, что список значений пуст?

Условие Если СписокЗначений.Количество() = 0 Тогда. Отдельного метода «Пустой» у списка нет. Эту проверку стоит делать перед любым обращением по индексу и перед передачей списка в запрос, чтобы не строить заведомо пустой отбор.

Чем список значений отличается от массива при обходе?

Оба обходятся одинаковыми циклами и индексируются с нуля. Различие в содержимом: элемент массива — это просто значение, а элемент списка — объект с Значением, Представлением, Пометкой и Картинкой. Список также можно показывать пользователю диалогами выбора и отметки, массив — нельзя.

Как обойти только помеченные элементы?

Полного отбора «по пометке» у списка нет. Обходите весь список циклом «Для Каждого» и внутри проверяйте Если Элемент.Пометка Тогда. Если помеченные элементы нужны многократно, соберите их один раз в отдельный массив или список и дальше работайте с ним.

Почему НайтиПоЗначению не находит существующий элемент?

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

Что быстрее: «Для Каждого» или цикл по индексу?

Для списка значений разница в типовых задачах незначительна, выбирайте по удобочитаемости. Заметный выигрыш даёт не смена цикла, а отказ от многократного линейного поиска НайтиПоЗначению внутри другого цикла — там помогает соответствие или индексированная таблица значений.