При разработке в среде «1С:Предприятие 8» программисты регулярно сталкиваются с необходимостью копирования данных. Типичный сценарий: у нас есть строка таблицы значений (или структура), мы создаем ее копию, переносим свойства с помощью встроенной функции ЗаполнитьЗначенияСвойств, а затем пытаемся изменить какое-то значение в новой строке. Однако внезапно обнаруживается, что изменения отразились и на исходном объекте. Почему так происходит, как устроена физика этого процесса в платформе 1С и какие существуют надежные способы обхода этой проблемы? Разберем все нюансы по шагам.
Для начала проанализируем исходную ситуацию. У нас есть код, который пытается продублировать команду печати в таблице значений, чтобы добавить к новой команде дополнительный параметр в структуру ДополнительныеПараметры:
КомандыПечатиСчетаНаОплату = КомандыПечати.НайтиСтроки(Новый Структура("МенеджерПечати", "Обработка.ПечатьСчетовНаОплату"));
Для каждого КомандаПечати Из КомандыПечатиСчетаНаОплату Цикл
НоваяКоманда = КомандыПечати.Добавить();
ЗаполнитьЗначенияСвойств(НоваяКоманда, КомандаПечати);
НоваяКоманда.Представление = НоваяКоманда.Представление + " на фирменном бланке";
НоваяКоманда.ДополнительныеПараметры.Вставить("НаФирменномБланке", Истина);
КонецЦикла;
При выполнении этого кода разработчик замечает странное поведение: свойство "НаФирменномБланке" со значением Истина добавляется не только в созданную строку НоваяКоманда, но и в исходную строку КомандаПечати.
Дело в том, что в платформе 1С все сложные коллекции данных (такие как Структура, Массив, Соответствие, ТаблицаЗначений) являются ссылочными типами данных. Они хранятся в оперативной памяти как объекты, а переменные содержат лишь адреса (указатели) на эти объекты.
Когда мы вызываем функцию ЗаполнитьЗначенияСвойств(НоваяКоманда, КомандаПечати), платформа выполняет так называемое поверхностное копирование (shallow copy):
СправочникСсылка.Организации) копируются непосредственно в виде значений. Это безопасно.ДополнительныеПараметры) копируются по ссылке. Это значит, что в свойство НоваяКоманда.ДополнительныеПараметры записывается тот же самый адрес в памяти, по которому расположена структура исходной команды.В результате обе переменные — и КомандаПечати.ДополнительныеПараметры, и НоваяКоманда.ДополнительныеПараметры — начинают ссылаться на один и тот же физический объект в оперативной памяти. Изменение структуры по любой из этих ссылок мгновенно изменяет общий объект. Чтобы решить эту проблему, нам необходимо выполнить глубокое копирование (deep copy).
Ниже мы подробно рассмотрим 5 практических способов реализации глубокого копирования в 1С:Предприятии.
Самый очевидный способ решить проблему «в лоб» без привлечения сторонних инструментов — исключить проблемное свойство из общего процесса заполнения свойств и скопировать его элементы вручную. Рассмотрим пример кода:
// Исключаем автоматическое копирование ссылки на структуру ДополнительныеПараметры
ЗаполнитьЗначенияСвойств(НоваяКоманда, КомандаПечати, , "ДополнительныеПараметры");
// Создаем новую чистую структуру для новой команды
НоваяКоманда.ДополнительныеПараметры = Новый Структура;
// В цикле переносим элементы из исходной структуры в новую
Для каждого КлючИЗначение Из КомандаПечати.ДополнительныеПараметры Цикл
НоваяКоманда.ДополнительныеПараметры.Вставить(КлючИЗначение.Ключ, КлючИЗначение.Значение);
КонецЦикла;
// Теперь спокойно добавляем новое уникальное значение
НоваяКоманда.ДополнительныеПараметры.Вставить("НаФирменномБланке", Истина);
Преимущества метода: Полная независимость от внешних модулей и библиотек. Код работает быстро и предсказуемо.
Недостатки метода: Метод эффективен только для одного уровня вложенности. Если бы внутри структуры ДополнительныеПараметры лежал еще один массив или структура, они бы снова скопировались по ссылке, и нам потребовались бы вложенные циклы.
Если вы ведете разработку в конфигурации, созданной на базе Библиотеки Стандартных Подсистем (БСП) — а это практически любые типовые конфигурации (УТ, ERP, КА, БП, ЗУП, УНФ), — вам не нужно изобретать велосипед. В составе БСП есть отличный готовый инструмент.
Для создания полной копии любого объекта или коллекции данных достаточно вызвать метод общего модуля:
// На сервере
НоваяКоманда.ДополнительныеПараметры = ОбщегоНазначения.СкопироватьРекурсивно(КомандаПечати.ДополнительныеПараметры);
// На клиенте
НоваяКоманда.ДополнительныеПараметры = ОбщегоНазначенияКлиент.СкопироватьРекурсивно(КомандаПечати.ДополнительныеПараметры);
Функция СкопироватьРекурсивно обходит всю иерархию переданного объекта и создает абсолютно независимые копии всех вложенных структур, массивов, соответствий и таблиц значений. Ссылки на элементы базы данных (справочники, документы) при этом остаются обычными ссылками, что и требуется в абсолютном большинстве бизнес-сценариев.
Преимущества метода: Надежное стандартное решение, протестированное на тысячах внедрений. Код пишется в одну строку и легко читается.
Недостатки метода: Требуется наличие БСП в вашей конфигурации.
Если в вашей конфигурации нет БСП, но вам нужно универсальное решение для глубокого копирования иерархических структур данных без ручного обхода циклов, отличным выбором станет сериализация в формат JSON. Этот механизм поддерживается платформой 1С нативно и работает на высокой скорости.
Рассмотрим алгоритм реализации:
// Сериализуем исходную структуру в строку JSON
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, КомандаПечати.ДополнительныеПараметры);
СтрокаJSON = ЗаписьJSON.Закрыть();
// Десериализуем строку обратно в совершенно новый объект
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.ОткрытьСтроку(СтрокаJSON);
НоваяКоманда.ДополнительныеПараметры = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();
// Добавляем новое свойство в изолированную структуру
НоваяКоманда.ДополнительныеПараметры.Вставить("НаФирменномБланке", Истина);
Преимущества метода: Метод универсален, работает как на клиенте, так и на сервере, не требует БСП и гарантирует 100% глубокое копирование всей вложенной иерархии.
Недостатки метода: JSON поддерживает ограниченный набор типов данных (числа, строки, даты, булевы, массивы, структуры). Если внутри копируемой структуры содержатся ссылки на объекты базы данных (например, СправочникСсылка), при стандартной десериализации они превратятся в строковые представления уникальных идентификаторов (GUID). Для сохранения типов 1С потребуется тонкая настройка сериализатора.
Для копирования структур со сложными типами данных (включая ссылки 1С, перечисления и специфические системные типы), когда JSON может исказить типы данных, применяется сериализация через механизмы XDTO:
ОбъектXDTO = СериализаторXDTO.ЗаписатьXDTO(КомандаПечати.ДополнительныеПараметры);
НоваяКоманда.ДополнительныеПараметры = СериализаторXDTO.ПрочитатьXDTO(ОбъектXDTO);
Преимущества метода: Фабрика XDTO бережно сохраняет все типы метаданных платформы «1С:Предприятие», корректно воссоздавая ссылки на справочники, документы и перечисления в новой копии объекта.
Недостатки метода: Требует контекста сервера и работает медленнее по сравнению с JSON из-за избыточности формата XML.
Иногда в старом коде или на форумах можно встретить следующий лаконичный способ создания копии:
КопияОбъекта = ЗначениеИзСтрокиВнутр(ЗначениеВСтрокуВнутр(ИсходныйОбъект));
Этот способ пришел к нам из версии 1С 7.7. Он действительно выполняет глубокое копирование любых объектов 1С в одну строчку кода, однако использовать его в современных конфигурациях категорически не рекомендуется по следующим причинам:
В завершение разберем полезный прием платформы. Если вам нужно быстро скопировать простую структуру без вложенных коллекций (то есть «плоскую» структуру, где все значения — это простые типы или ссылки), нет необходимости писать циклы или вызывать функции сериализации.
Платформа позволяет инициализировать новую структуру на основании существующей через ее конструктор:
// ОригинальнаяПлоскаяСтруктура — исходный объект
КопияСтруктуры = Новый Структура(ОригинальнаяПлоскаяСтруктура);
В результате будет создана новая независимая структура с точно такими же ключами и значениями. Но помните: если внутри исходной структуры все же окажется массив или другая структура, они скопируются по ссылке, так как данный метод тоже выполняет лишь поверхностное копирование.
Чтобы избежать трудноуловимых багов со связанными значениями при разработке в 1С, придерживайтесь простых правил:
ОбщегоНазначения.СкопироватьРекурсивно().ЗначениеВСтрокуВнутр и ЗначениеИзСтрокиВнутр в новом коде.