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