Каждый начинающий разработчик в 1С сталкивается с ситуацией, когда при удалении строк из табличной части или таблицы значений часть строк неожиданно пропускается. Разберем механику этой проблемы, выясним причину ее возникновения и рассмотрим надежные способы корректного удаления строк из коллекций.
Проанализируем стандартный ошибочный пример, с которого часто начинаются подобные трудности:
Для Каждого Строка ИЗ Объект.ТабличнаяЧасть Цикл
Если УсловиеВыполнено Тогда
Объект.ТабличнаяЧасть.Удалить(Строка);
КонецЕсли;
КонецЦикла;
В этом сценарии итератор цикла переходит к следующему элементу по порядковому номеру (индексу). Предположим, у нас есть строки с индексами 0, 1, 2 и 3. На первом шаге удаляется строка с индексом 0. В момент вызова метода Удалить() коллекция немедленно сдвигается: строка, которая была на позиции 1, теперь занимает позицию 0, а бывшая вторая строка становится первой. На следующей итерации цикл переходит к индексу 1, в результате чего строка, переместившаяся на позицию 0, оказывается полностью пропущенной.
Чтобы избежать подобных коллизий со смещением индексов, рассмотрим несколько надежных подходов к решению этой задачи.
Наиболее наглядный, безопасный и универсальный способ — сначала отобрать все строки, подлежащие удалению, в отдельный Массив, а затем удалить их отдельным циклом. В таком случае обход исходной коллекции не нарушается, так как во время первого цикла модификация табличной части не происходит.
Разберем по шагам реализацию данного подхода:
Для Каждого по табличной части и складываем подходящие строки в массив.Посмотрим на пример программного кода:
МассивДляУдаления = Новый Массив;
// Первый проход: сбор кандидатов на удаление
Для Каждого СтрокаТЧ Из Объект.ТабличнаяЧасть Цикл
Если СтрокаТЧ.Количество = 0 Тогда
МассивДляУдаления.Добавить(СтрокаТЧ);
КонецЕсли;
КонецЦикла;
// Второй проход: непосредственное удаление
Для Каждого СтрокаТЧ Из МассивДляУдаления Цикл
Объект.ТабличнаяЧасть.Удалить(СтрокаТЧ);
КонецЦикла;
Этот способ отлично подходит для большинства прикладных задач, обеспечивает легкую читаемость кода и гарантирует, что ни одна строка не будет пропущена.
Другой классический алгоритм — обходить строки в обратном направлении: от последней к первой. При удалении строки с конца смещаются только те элементы, индексы которых больше текущего. Поскольку мы движемся назад к нулевому индексу, оставшиеся впереди строки не меняют своих позиций относительно счетчика цикла.
Обратим внимание на важную особенность платформы 1С:Предприятие: конструкция цикла Для ... По ... Цикл умеет изменять шаг счетчика только на +1. Написать обратный шаг напрямую в стандартном цикле Для нельзя. Поэтому обход с конца выполняется с помощью цикла Пока.
Посмотрим на пример реализации через цикл Пока:
Индекс = Объект.ТабличнаяЧасть.Количество() - 1;
Пока Индекс >= 0 Цикл
СтрокаТЧ = Объект.ТабличнаяЧасть[Индекс];
Если СтрокаТЧ.Количество = 0 Тогда
Объект.ТабличнаяЧасть.Удалить(Индекс);
КонецЕсли;
Индекс = Индекс - 1;
КонецЦикла;
В этом варианте метод Удалить() принимает параметром сам Индекс строки. Этот способ не требует выделения дополнительной памяти под вспомогательный массив.
Если по каким-то причинам требуется идти от первой строки к последней, но без создания массива, можно использовать цикл Пока с условным увеличением индекса. Главное правило: увеличивать индекс нужно только тогда, когда строка не была удалена. Если строка удаляется, следующая за ней строка автоматически занимает текущий индекс, и смещать счетчик не требуется.
Разберем этот вариант на примере:
Индекс = 0;
Пока Индекс < Объект.ТабличнаяЧасть.Количество() Цикл
СтрокаТЧ = Объект.ТабличнаяЧасть[Индекс];
Если СтрокаТЧ.Количество = 0 Тогда
Объект.ТабличнаяЧасть.Удалить(Индекс);
Иначе
Индекс = Индекс + 1;
КонецЕсли;
КонецЦикла;
Если задача заключается в удалении строк, которые пользователь выделил в таблице на форме вручную (мышью или комбинацией клавиш Shift/Ctrl), обходить всю табличную часть вовсе не требуется. У элемента формы есть специальное свойство ВыделенныеСтроки, содержащее коллекцию идентификаторов выделенных позиций.
Рассмотрим, как правильно обработать удаление выделенных строк:
Для Каждого Идентификатор Из Элементы.ТабличнаяЧасть.ВыделенныеСтроки Цикл
Строка = Объект.ТабличнаяЧасть.НайтиПоИдентификатору(Идентификатор);
Если Строка <> Неопределено И УсловиеВыполнено Тогда
Объект.ТабличнаяЧасть.Удалить(Строка);
КонецЕсли;
КонецЦикла;
Важно помнить: при вызове метода Удалить() платформа выполняет физический сдвиг всех последующих элементов в оперативной памяти. Если в табличной части содержатся десятки тысяч строк, массовое удаление по одной строке в цикле может работать крайне медленно.
Для ускорения обработки сотен и тысяч строк рекомендуется инвертировать логику: скопировать или отобрать строки, которые должны остаться, затем очистить коллекцию методом Очистить() и восстановить нужные данные методом Загрузить(). Это сократит время выполнения операции в разы.