Почему ЗаполнитьЗначенияСвойств() меняет данные в источнике при работе с вложенными Таблицами Значений?

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

При разработке сложных алгоритмов в 1С часто возникает необходимость копирования данных между строками таблиц значений. Стандартным и наиболее быстрым инструментом для этого является функция ЗаполнитьЗначенияСвойств(). Однако при работе с вложенными структурами данных, такими как колонки с типом ТаблицаЗначений или Массив, разработчики сталкиваются с коварным поведением системы: изменения в новой строке-приемнике внезапно отражаются в строке-источнике. Разберем подробно, почему так происходит и как правильно организовать копирование данных.

Суть проблемы: Поверхностное копирование

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

Проанализируем фрагмент кода, вызывающий ошибку:


НовоеНачисление = Начисления.Добавить();
ЗаполнитьЗначенияСвойств(НовоеНачисление, СтрокаПоказателей);

// После выполнения этой строки НовоеНачисление.Показатели указывает на ту же таблицу в памяти, 
// что и СтрокаПоказателей.Показатели

НовоеНачисление.Показатели[0].Значение = РезультатЗапросаВыборка.ПроцентПерсональнойНадбавки; 
// ОШИБКА: значение изменится и в исходной СтрокаПоказателей!

Выясним причину такого поведения. Функция ЗаполнитьЗначенияСвойств() реализует механизм поверхностного копирования. Для примитивных типов (числа, строки, даты) она копирует само значение. Но для сложных типов (объектов) она копирует ссылку на объект в оперативной памяти. Поскольку ТаблицаЗначений является объектом, в колонку Показатели новой строки записывается адрес той же самой таблицы, которая лежит в исходной строке. В итоге две разные строки ссылаются на один и тот же физический экземпляр вложенной таблицы.

Разбор по шагам: Как избежать связи между объектами

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

  1. Использование списка исключений. У функции ЗаполнитьЗначенияСвойств() есть третий параметр — СписокИсключаемыхСвойств. Мы должны указать там колонку с коллекцией, чтобы не допустить копирования ссылки.
  2. Явное копирование вложенной коллекции. После копирования простых свойств необходимо вызвать метод Скопировать() для вложенной таблицы.

Посмотрим на пример правильной реализации:


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

Альтернативный вариант: Построчный перенос данных

В некоторых случаях метод Скопировать() может быть избыточен, если нам нужно перенести не все данные или если структура вложенной таблицы в приемнике отличается. Рассмотрим вариант с ручным обходом вложенной коллекции, предложенный в обсуждении:


НовоеНачисление = Начисления.Добавить();
ЗаполнитьЗначенияСвойств(НовоеНачисление, СтрокаПоказателей, "Показатели");

// Гарантируем, что в новой строке пустая и независимая коллекция
НовоеНачисление.Показатели.Очистить(); 

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

Этот способ более трудоемок в написании, но дает полный контроль над процессом формирования данных в каждой вложенной строке.

Объекты, требующие особого внимания

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

Практические рекомендации для разработчиков

Для обеспечения надежности кода при работе с коллекциями рекомендуется придерживаться следующих правил:

Во-первых, всегда используйте отладчик. Если вы видите, что значение в переменной-источнике меняется "само по себе" после выполнения присваивания в другой переменной — это верный признак дублирования ссылок на один объект.

Во-вторых, если вы часто выполняете глубокое копирование сложных структур, можно написать универсальную процедуру, которая будет обходить свойства объекта и, обнаружив тип ТаблицаЗначений или Массив, вызывать их методы копирования автоматически.

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

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

← На главную