Почему при изменении значения переменной меняются данные в массиве и как этого избежать?

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

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

Разбор ситуации: почему "А = В" не всегда создает копию?

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


МассивЗаписей = Новый Массив;
Для Каждого Запись Из НаборЗаписей Цикл
    Если Запись.Количество > 0 Тогда
        ЗаписьПродажи = Запись; // Казалось бы, сохраняем "снимок"
        МассивЗаписей.Добавить(ЗаписьПродажи);
        
        // Теперь меняем значение в исходной записи
        Запись.Количество = 50; 
        
        // Ожидание: в МассивЗаписей[0].Количество осталось старое значение
        // Реальность: в МассивЗаписей[0].Количество теперь тоже 50
    КонецЕсли;
КонецЦикла;

Выясним причину такого поведения. Проблема кроется в фундаментальном различии между примитивными и ссылочными типами данных.

Типы данных: Примитивы против Объектов

В 1С все типы данных можно разделить на две большие группы:

  1. Примитивные типы: Это Число, Строка, Дата и Булево. Когда вы выполняете присваивание А = Б для этих типов, система создает в памяти полную копию значения. Если Б равно 100, то А тоже станет равно 100, и это будут две независимые ячейки памяти.
  2. Ссылочные (объектные) типы: К ним относятся Массив, Структура, ТаблицаЗначений, а также строки наборов записей и объекты метаданных (СправочникОбъект, ДокументОбъект). Для этих типов переменная хранит не сами данные, а адрес (ссылку) на область памяти, где эти данные лежат.

Когда мы пишем ЗаписьПродажи = Запись, мы не копируем содержимое строки регистра. Мы просто создаем еще один "указатель", который смотрит на то же самое место в памяти. Поэтому, когда мы меняем Запись.Количество, мы меняем данные по адресу, на который ссылаются обе переменные — и Запись, и элемент массива.

Решение 1: Ручное копирование значений в Структуру

Самый простой и понятный способ "разорвать связь" — это создать новый объект (например, структуру) и перенести в него только нужные значения. Рассмотрим, как это реализовать на практике.

Вместо того чтобы добавлять в массив саму запись, мы создадим независимый контейнер:


МассивЗаписей = Новый Массив;
Для Каждого Запись Из НаборЗаписей Цикл
    Если Запись.Количество > 0 Тогда
        // Создаем новый объект в памяти
        ДанныеДляСохранения = Новый Структура;
        ДанныеДляСохранения.Вставить("Количество", Запись.Количество);
        ДанныеДляСохранения.Вставить("Номенклатура", Запись.Номенклатура);
        
        // Теперь в массиве лежит ссылка на структуру, а не на строку набора
        МассивЗаписей.Добавить(ДанныеДляСохранения);
        
        // Теперь это изменение не затронет данные в массиве
        Запись.Количество = 50; 
    КонецЕсли;
КонецЦикла;

Решение 2: Использование универсального метода ЗаполнитьЗначенияСвойств

Если полей в записи много (например, десятки ресурсов и измерений), прописывать каждое поле вручную долго и чревато ошибками. Проанализируем более эффективный метод — использование встроенной функции ЗаполнитьЗначенияСвойств().

Этот метод копирует значения всех совпадающих свойств из одного объекта в другой. Разберем по шагам, как это применить:


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

Важный нюанс: Если свойство объекта само является ссылочным типом (например, другая структура), то ЗаполнитьЗначенияСвойств скопирует ссылку на неё, а не создаст копию вложенного объекта. Но для реквизитов регистра (Числа, Ссылки на справочники) это работает идеально.

Решение 3: Выгрузка в Таблицу Значений

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

Рассмотрим пример:


// Создаем полную копию данных набора в памяти
ТаблицаКопия = НаборЗаписей.Выгрузить();

// Теперь мы можем свободно менять исходный набор записей
Для Каждого Запись Из НаборЗаписей Цикл
    Запись.Количество = 50;
КонецЦикла;

// В переменной ТаблицаКопия данные остались прежними (исходными)

Этот способ является наиболее производительным, когда требуется "заморозить" состояние большого объема данных перед их массовой обработкой.

Продвинутый метод: Глубокое копирование через Хранилище Значения

Иногда возникает задача создать абсолютно независимую копию сложного объекта (например, массива структур, где внутри структур есть другие массивы). В 1С нет встроенного метода Клонировать() для всех типов, поэтому программисты используют "трюк" с сериализацией.

Разберем, как это работает:

  1. Мы помещаем сложный объект в ХранилищеЗначения. При этом 1С превращает объект в последовательность байтов (сериализует его).
  2. Мы извлекаем объект обратно методом Получить().

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


// Предположим, у нас есть сложная структура
Оригинал = Новый Структура("Данные", МойМассивОбъектов);

// Создаем глубокую копию
Хранилище = Новый ХранилищеЗначения(Оригинал);
Копия = Хранилище.Получить();

// Теперь Оригинал и Копия живут своей жизнью

Подведем итоги

Чтобы не допускать ошибок при работе с коллекциями и объектами в 1С, важно помнить следующие правила:

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

← На главную