Почему ЗаполнитьЗначенияСвойств изменяет исходную структуру в 1С и как правильно выполнить глубокое копирование?

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

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

Выясняем причину: поверхностное копирование vs глубокое копирование

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


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

При выполнении этого кода разработчик замечает странное поведение: свойство "НаФирменномБланке" со значением Истина добавляется не только в созданную строку НоваяКоманда, но и в исходную строку КомандаПечати.

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

Когда мы вызываем функцию ЗаполнитьЗначенияСвойств(НоваяКоманда, КомандаПечати), платформа выполняет так называемое поверхностное копирование (shallow copy):

  1. Простые типы данных (числа, строки, даты, булевы значения), а также ссылки на объекты базы данных (например, СправочникСсылка.Организации) копируются непосредственно в виде значений. Это безопасно.
  2. Сложные объекты (в данном случае структура ДополнительныеПараметры) копируются по ссылке. Это значит, что в свойство НоваяКоманда.ДополнительныеПараметры записывается тот же самый адрес в памяти, по которому расположена структура исходной команды.

В результате обе переменные — и КомандаПечати.ДополнительныеПараметры, и НоваяКоманда.ДополнительныеПараметры — начинают ссылаться на один и тот же физический объект в оперативной памяти. Изменение структуры по любой из этих ссылок мгновенно изменяет общий объект. Чтобы решить эту проблему, нам необходимо выполнить глубокое копирование (deep copy).

Ниже мы подробно рассмотрим 5 практических способов реализации глубокого копирования в 1С:Предприятии.

Способ 1. Исключение свойства и ручной обход во вложенном цикле

Самый очевидный способ решить проблему «в лоб» без привлечения сторонних инструментов — исключить проблемное свойство из общего процесса заполнения свойств и скопировать его элементы вручную. Рассмотрим пример кода:


// Исключаем автоматическое копирование ссылки на структуру ДополнительныеПараметры
ЗаполнитьЗначенияСвойств(НоваяКоманда, КомандаПечати, , "ДополнительныеПараметры");

// Создаем новую чистую структуру для новой команды
НоваяКоманда.ДополнительныеПараметры = Новый Структура;

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

// Теперь спокойно добавляем новое уникальное значение
НоваяКоманда.ДополнительныеПараметры.Вставить("НаФирменномБланке", Истина);

Преимущества метода: Полная независимость от внешних модулей и библиотек. Код работает быстро и предсказуемо.

Недостатки метода: Метод эффективен только для одного уровня вложенности. Если бы внутри структуры ДополнительныеПараметры лежал еще один массив или структура, они бы снова скопировались по ссылке, и нам потребовались бы вложенные циклы.

Способ 2. Использование функции БСП «СкопироватьРекурсивно» (Рекомендуемый)

Если вы ведете разработку в конфигурации, созданной на базе Библиотеки Стандартных Подсистем (БСП) — а это практически любые типовые конфигурации (УТ, ERP, КА, БП, ЗУП, УНФ), — вам не нужно изобретать велосипед. В составе БСП есть отличный готовый инструмент.

Для создания полной копии любого объекта или коллекции данных достаточно вызвать метод общего модуля:


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

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

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

Преимущества метода: Надежное стандартное решение, протестированное на тысячах внедрений. Код пишется в одну строку и легко читается.

Недостатки метода: Требуется наличие БСП в вашей конфигурации.

Способ 3. Глубокое копирование через сериализацию в JSON (Для самописных конфигураций)

Если в вашей конфигурации нет БСП, но вам нужно универсальное решение для глубокого копирования иерархических структур данных без ручного обхода циклов, отличным выбором станет сериализация в формат JSON. Этот механизм поддерживается платформой 1С нативно и работает на высокой скорости.

Рассмотрим алгоритм реализации:


// Сериализуем исходную структуру в строку JSON
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, КомандаПечати.ДополнительныеПараметры);
СтрокаJSON = ЗаписьJSON.Закрыть();

// Десериализуем строку обратно в совершенно новый объект
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.ОткрытьСтроку(СтрокаJSON);
НоваяКоманда.ДополнительныеПараметры = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();

// Добавляем новое свойство в изолированную структуру
НоваяКоманда.ДополнительныеПараметры.Вставить("НаФирменномБланке", Истина);

Преимущества метода: Метод универсален, работает как на клиенте, так и на сервере, не требует БСП и гарантирует 100% глубокое копирование всей вложенной иерархии.

Недостатки метода: JSON поддерживает ограниченный набор типов данных (числа, строки, даты, булевы, массивы, структуры). Если внутри копируемой структуры содержатся ссылки на объекты базы данных (например, СправочникСсылка), при стандартной десериализации они превратятся в строковые представления уникальных идентификаторов (GUID). Для сохранения типов 1С потребуется тонкая настройка сериализатора.

Способ 4. Сериализация через XML/XDTO

Для копирования структур со сложными типами данных (включая ссылки 1С, перечисления и специфические системные типы), когда JSON может исказить типы данных, применяется сериализация через механизмы XDTO:


ОбъектXDTO = СериализаторXDTO.ЗаписатьXDTO(КомандаПечати.ДополнительныеПараметры);
НоваяКоманда.ДополнительныеПараметры = СериализаторXDTO.ПрочитатьXDTO(ОбъектXDTO);

Преимущества метода: Фабрика XDTO бережно сохраняет все типы метаданных платформы «1С:Предприятие», корректно воссоздавая ссылки на справочники, документы и перечисления в новой копии объекта.

Недостатки метода: Требует контекста сервера и работает медленнее по сравнению с JSON из-за избыточности формата XML.

Способ 5. Устаревший метод ЗначениеВСтрокуВнутр (Не рекомендуется к использованию)

Иногда в старом коде или на форумах можно встретить следующий лаконичный способ создания копии:

КопияОбъекта = ЗначениеИзСтрокиВнутр(ЗначениеВСтрокуВнутр(ИсходныйОбъект));

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

Полезный лайфхак: Поверхностное копирование «плоских» структур конструктором

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

Платформа позволяет инициализировать новую структуру на основании существующей через ее конструктор:


// ОригинальнаяПлоскаяСтруктура — исходный объект
КопияСтруктуры = Новый Структура(ОригинальнаяПлоскаяСтруктура);

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

Резюмируем лучшие практики

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

  1. Всегда помните, что структуры, массивы и таблицы значений передаются и заполняются по ссылке (поверхностное копирование).
  2. При работе в типовых конфигурациях с БСП для клонирования всегда применяйте ОбщегоНазначения.СкопироватьРекурсивно().
  3. В изолированных и самописных базах без БСП используйте сериализацию JSON для простых типов или сериализацию XDTO для сложных типов данных.
  4. Избегайте использования устаревших функций ЗначениеВСтрокуВнутр и ЗначениеИзСтрокиВнутр в новом коде.
← На главную