В практике разработки на платформе 1С:Предприятие начинающие специалисты часто сталкиваются с ситуацией, которая на первый взгляд кажется парадоксальной: вы записываете объект в массив или другую коллекцию, затем меняете исходный объект, и внезапно обнаруживаете, что данные внутри массива тоже изменились. Это вызывает недоумение, особенно у тех, кто привык работать с простыми переменными. В этой статье мы подробно разберем, почему так происходит, изучим механизмы работы памяти в 1С и рассмотрим несколько эффективных способов решения этой задачи.
Давайте проанализируем пример кода, который вызвал затруднения. Представим цикл по набору записей регистра. Разработчик хочет сохранить текущее состояние записи в массив, чтобы использовать его позже, а затем изменить данные в самом наборе записей.
МассивЗаписей = Новый Массив;
Для Каждого Запись Из НаборЗаписей Цикл
Если Запись.Количество > 0 Тогда
ЗаписьПродажи = Запись; // Казалось бы, сохраняем "снимок"
МассивЗаписей.Добавить(ЗаписьПродажи);
// Теперь меняем значение в исходной записи
Запись.Количество = 50;
// Ожидание: в МассивЗаписей[0].Количество осталось старое значение
// Реальность: в МассивЗаписей[0].Количество теперь тоже 50
КонецЕсли;
КонецЦикла;
Выясним причину такого поведения. Проблема кроется в фундаментальном различии между примитивными и ссылочными типами данных.
В 1С все типы данных можно разделить на две большие группы:
Число, Строка, Дата и Булево. Когда вы выполняете присваивание А = Б для этих типов, система создает в памяти полную копию значения. Если Б равно 100, то А тоже станет равно 100, и это будут две независимые ячейки памяти.Массив, Структура, ТаблицаЗначений, а также строки наборов записей и объекты метаданных (СправочникОбъект, ДокументОбъект). Для этих типов переменная хранит не сами данные, а адрес (ссылку) на область памяти, где эти данные лежат.Когда мы пишем ЗаписьПродажи = Запись, мы не копируем содержимое строки регистра. Мы просто создаем еще один "указатель", который смотрит на то же самое место в памяти. Поэтому, когда мы меняем Запись.Количество, мы меняем данные по адресу, на который ссылаются обе переменные — и Запись, и элемент массива.
Самый простой и понятный способ "разорвать связь" — это создать новый объект (например, структуру) и перенести в него только нужные значения. Рассмотрим, как это реализовать на практике.
Вместо того чтобы добавлять в массив саму запись, мы создадим независимый контейнер:
МассивЗаписей = Новый Массив;
Для Каждого Запись Из НаборЗаписей Цикл
Если Запись.Количество > 0 Тогда
// Создаем новый объект в памяти
ДанныеДляСохранения = Новый Структура;
ДанныеДляСохранения.Вставить("Количество", Запись.Количество);
ДанныеДляСохранения.Вставить("Номенклатура", Запись.Номенклатура);
// Теперь в массиве лежит ссылка на структуру, а не на строку набора
МассивЗаписей.Добавить(ДанныеДляСохранения);
// Теперь это изменение не затронет данные в массиве
Запись.Количество = 50;
КонецЕсли;
КонецЦикла;
Если полей в записи много (например, десятки ресурсов и измерений), прописывать каждое поле вручную долго и чревато ошибками. Проанализируем более эффективный метод — использование встроенной функции ЗаполнитьЗначенияСвойств().
Этот метод копирует значения всех совпадающих свойств из одного объекта в другой. Разберем по шагам, как это применить:
Для Каждого Запись Из НаборЗаписей Цикл
// Подготовим структуру со всеми полями, которые есть в записи
СнимокДанных = Новый Структура("Количество, Сумма, Номенклатура, Склад");
// Копируем значения из записи в структуру "по значению" (для примитивных типов)
ЗаполнитьЗначенияСвойств(СнимокДанных, Запись);
МассивЗаписей.Добавить(СнимокДанных);
КонецЦикла;
Важный нюанс: Если свойство объекта само является ссылочным типом (например, другая структура), то ЗаполнитьЗначенияСвойств скопирует ссылку на неё, а не создаст копию вложенного объекта. Но для реквизитов регистра (Числа, Ссылки на справочники) это работает идеально.
Если вам нужно сохранить состояние всего набора записей целиком, лучше всего воспользоваться методом Выгрузить(). Этот метод создает полную независимую копию данных набора в виде таблицы значений.
Рассмотрим пример:
// Создаем полную копию данных набора в памяти
ТаблицаКопия = НаборЗаписей.Выгрузить();
// Теперь мы можем свободно менять исходный набор записей
Для Каждого Запись Из НаборЗаписей Цикл
Запись.Количество = 50;
КонецЦикла;
// В переменной ТаблицаКопия данные остались прежними (исходными)
Этот способ является наиболее производительным, когда требуется "заморозить" состояние большого объема данных перед их массовой обработкой.
Иногда возникает задача создать абсолютно независимую копию сложного объекта (например, массива структур, где внутри структур есть другие массивы). В 1С нет встроенного метода Клонировать() для всех типов, поэтому программисты используют "трюк" с сериализацией.
Разберем, как это работает:
ХранилищеЗначения. При этом 1С превращает объект в последовательность байтов (сериализует его).Получить().В результате создается абсолютно новый объект со всей вложенной иерархией данных, никак не связанный с оригиналом. Посмотрим на пример кода:
// Предположим, у нас есть сложная структура
Оригинал = Новый Структура("Данные", МойМассивОбъектов);
// Создаем глубокую копию
Хранилище = Новый ХранилищеЗначения(Оригинал);
Копия = Хранилище.Получить();
// Теперь Оригинал и Копия живут своей жизнью
Чтобы не допускать ошибок при работе с коллекциями и объектами в 1С, важно помнить следующие правила:
ДокументОбъект или СтрокуТаблицыЗначений в массив, вы добавили ссылку на них, а не их данные.ЗаполнитьЗначенияСвойств() для быстрого копирования полей.Выгрузить().Понимание механизмов работы со ссылочными типами данных — это тот фундамент, который позволяет писать надежный и предсказуемый код. Надеемся, что этот разбор помог вам разобраться в причинах "магического" изменения количества и теперь ваши данные всегда будут под контролем.