Почему не удаляются все строки в цикле и как правильно очищать коллекции в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

Посмотрим на типичный ошибочный код, в котором проявляется проблема:


Процедура УдалитьСтрокиУслугиНаСервере()
    Для Каждого стр Из Объект.Услуги Цикл
        Если стр.Количество = 0 Тогда
            Объект.Услуги.Удалить(стр);
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Почему при прямом обходе удаляются не все строки

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

Когда мы вызываем метод Удалить(), происходят следующие изменения:

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

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

Способ 1. Предварительный сбор строк в массив (универсальный подход)

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


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

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

Способ 2. Поиск строк через метод НайтиСтроки

Если критерий отбора представляет собой точное равенство определенного реквизита (например, Количество = 0), мы можем использовать встроенный метод коллекции НайтиСтроки(). Этот метод возвращает массив строк, полностью соответствующих структуре отбора.


Процедура УдалитьСтрокиУслугиНаСервере()
    ПараметрыОтбора = Новый Структура("Количество", 0);
    СтрокиКУдалению = Объект.Услуги.НайтиСтроки(ПараметрыОтбора);
    
    Для Каждого СтрокаКУдалению Из СтрокиКУдалению Цикл
        Объект.Услуги.Удалить(СтрокаКУдалению);
    КонецЦикла;
КонецПроцедуры

Важное ограничение: метод НайтиСтроки() выполняет поиск исключительно по строгому равенству значений. Если условие более сложное (например, Количество < 0 или проверка нескольких условий через «ИЛИ»), данный способ не подойдет и потребуется использовать сбор в массив с ручной проверкой или обход по индексам.

Способ 3. Обход коллекции с конца (обратный цикл)

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

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

Вариант через цикл Пока:


Процедура УдалитьСтрокиУслугиНаСервере()
    Индекс = Объект.Услуги.Количество() - 1;
    
    Пока Индекс >= 0 Цикл
        Если Объект.Услуги[Индекс].Количество = 0 Тогда
            Объект.Услуги.Удалить(Индекс);
        КонецЕсли;
        Индекс = Индекс - 1;
    КонецЦикла;
КонецПроцедуры

Вариант через цикл Для:


Процедура УдалитьСтрокиУслугиНаСервере()
    ВсегоСтрок = Объект.Услуги.Количество();
    
    Для Сч = 1 По ВсегоСтрок Цикл
        Индекс = ВсегоСтрок - Сч;
        Если Объект.Услуги[Индекс].Количество = 0 Тогда
            Объект.Услуги.Удалить(Индекс);
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Удаление по индексу (Объект.Услуги.Удалить(Индекс)) выполняется быстрее, чем удаление по ссылке на строку, так как платформе не требуется искать объект в памяти коллекции.

Способ 4. Прямой цикл Пока с условным инкрементом

Если необходимо сохранить прямой порядок обхода коллекции без выделения дополнительной памяти под массив, можно управлять счетчиком индекса вручную. Увеличивать индекс следует только в том случае, если строка не была удалена:


Процедура УдалитьСтрокиУслугиНаСервере()
    Индекс = 0;
    
    Пока Индекс < Объект.Услуги.Количество() Цикл
        Если Объект.Услуги[Индекс].Количество = 0 Тогда
            Объект.Услуги.Удалить(Индекс);
        Иначе
            Индекс = Индекс + 1;
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Рекомендации по оптимизации при работе с большими таблицами

При обработке десятков тысяч строк построчное удаление из реквизита формы типа ДанныеФормыКоллекция может замедлять работу из-за частых синхронизаций интерфейсных данных. В таких случаях рекомендуется:

  1. Преобразовать реквизит формы в прикладной объект или таблицу значений с помощью метода РеквизитФормыВЗначение().
  2. Выполнить пакетную обработку или выгрузить данные в ТаблицаЗначений, отфильтровать нужные строки запросом или методом Скопировать().
  3. Загрузить обработанный результат обратно в реквизит формы с помощью функции ЗначениеВРеквизитФормы() или метода Загрузить().
← На главную