Почему при изменении элемента массива меняются другие элементы и как сделать "честную" копию в 1С

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

В практике разработки на платформе «1С:Предприятие 8» часто возникает ситуация, которая новичкам кажется багом платформы, а опытным разработчикам — очевидным поведением. Речь идет о работе с коллекциями (массивами, структурами, соответствиями) и о том, как именно они ведут себя при копировании и передаче в параметры процедур. Разберем ситуацию на конкретном примере, когда разработчик ожидает создания копии объекта, но на самом деле работает с тем же самым участком памяти.

Анализируем проблему: почему "дубли" в массиве меняются одновременно?

Рассмотрим классический пример кода, который вызывает недоумение. Допустим, мы создали массив, наполнили его структурами, а затем решили "размножить" эти записи внутри того же массива:


Структура1 = Новый Структура;
Структура1.Вставить("ИД", "100");
Структура1.Вставить("Параметр", "Тест1");

Структура2 = Новый Структура;
Структура2.Вставить("ИД", "101");
Структура2.Вставить("Параметр", "Тест2");

МассивДляТеста = Новый Массив;
МассивДляТеста.Добавить(Структура1);
МассивДляТеста.Добавить(Структура2);

// Попытка скопировать первые два элемента в конец массива
Для Инд = 0 По МассивДляТеста.ВГраница() Цикл
    МассивДляТеста.Добавить(МассивДляТеста[Инд]);
КонецЦикла;

// Попытка изменить ИД только у новых элементов (индексы 2 и 3)
Для Индекс = 2 ПО 3 Цикл
    Элемент = МассивДляТеста[Индекс];
    Элемент.ИД = "200";
    // Казалось бы, перезаписываем элемент в массиве
    МассивДляТеста.Установить(Индекс, Элемент);
КонецЦикла;

Проанализируем результат: после выполнения этого кода во всех четырех элементах массива поле ИД станет равным "200". Это происходит потому, что в 1С коллекции являются ссылочными типами данных.

Разберем по шагам, что произошло в памяти:

  1. Когда мы выполнили МассивДляТеста.Добавить(Структура1), в массив попала не сама структура, а ссылка (указатель) на объект в памяти.
  2. В цикле копирования МассивДляТеста.Добавить(МассивДляТеста[Инд]) мы просто взяли существующую ссылку и добавили её в массив еще раз. Теперь у нас в массиве четыре ячейки, но они указывают всего на два реальных объекта в памяти.
  3. Когда мы обращаемся к МассивДляТеста[2] и меняем поле ИД, мы меняем его непосредственно в объекте, на который ссылается и МассивДляТеста[0].

Метод Установить() в данном случае также не помогает, так как он просто записывает ту же самую ссылку поверх самой себя. Выясним причину: платформа не делает автоматического клонирования объектов при их перемещении внутри коллекций.

Решение №1: Использование БСП (ОбщегоНазначения.СкопироватьРекурсивно)

Если в вашей конфигурации используется Библиотека Стандартных Подсистем (БСП), самым простым и правильным решением будет использование функции СкопироватьРекурсивно. Посмотрим на примере, как это меняет поведение кода:


// Вместо прямого копирования ссылок используем функцию БСП
МассивДляТеста2 = ОбщегоНазначения.СкопироватьРекурсивно(МассивДляТеста);

Для Индекс = 2 ПО 3 Цикл
    Элемент = МассивДляТеста2[Индекс];
    Элемент.ИД = "200";
    // Теперь это сработает только для элементов 2 и 3
КонецЦикла;

Почему это работает? Данный метод обходит все элементы коллекции и для каждого объекта (структуры, массива, таблицы) создает новый экземпляр (через Новый Структура), копируя в него значения. Это называется "глубоким копированием" (Deep Copy).

Решение №2: Клонирование через сериализацию (XDTO или XML)

Если вам нужно создать полную независимую копию объекта, а БСП под рукой нет (или объект слишком сложный), можно воспользоваться механизмом сериализации. Проанализируем, как превратить объект в строку и обратно:


// Быстрый способ клонирования через XML
Запись = Новый ЗаписьXML;
Запись.УстановитьСтроку();
СериализаторXDTO.ЗаписатьXML(Запись, ИсходныйМассив);
СтрокаXML = Запись.Закрыть();

Чтение = Новый ЧтениеXML;
Чтение.УстановитьСтроку(СтрокаXML);
НовыйМассив = СериализаторXDTO.ПрочитатьXML(Чтение);

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

Решение №3: Трюк с фиксированными коллекциями

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


// Создаем копию структуры за одну строку
КопияСтруктуры = Новый Структура(Новый ФиксированнаяСтруктура(ИсходнаяСтруктура));

Здесь происходит следующее: конструктор ФиксированнаяСтруктура принимает обычную структуру и создает на её основе неизменяемый объект. Затем мы передаем этот объект обратно в конструктор обычной Структура, который создает новую изменяемую копию. Это гораздо быстрее, чем ручной перебор ключей в цикле.

Важные нюансы: передача между Клиентом и Сервером

Проанализируем ситуацию, которая часто путает разработчиков. Если вы передаете массив структур из клиентской процедуры в процедуру &НаСервере, платформа выполняет неявную сериализацию. Это означает, что на сервере будет создан новый объект. Если вы измените его на сервере, исходный объект на клиенте не изменится (если вы не вернете его обратно через возвращаемое значение функции или параметры).

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


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

В этом примере после выхода из процедуры МояСтруктура.Поле изменится и в вызывающем коде. Это критически важный момент для понимания архитектуры 1С.

Резюме и рекомендации

Подведем итоги нашего анализа работы с коллекциями в 1С:

  1. Помните о ссылочной природе: Присваивание Массив2 = Массив1 или Добавить(Элемент) не создает новый объект, а создает новую ссылку на старый.
  2. Используйте глубокое копирование: Если вам нужно изменить копию, не затрагивая оригинал, используйте ОбщегоНазначения.СкопироватьРекурсивно().
  3. Защищайте данные: Если вы возвращаете коллекцию из функции и хотите гарантировать, что её не изменят случайно, возвращайте ФиксированныйМассив или ФиксированнаяСтруктура. Это вызовет ошибку при попытке изменения, что гораздо проще отладить, чем "внезапное" изменение данных в неожиданных местах.
  4. Проверяйте контекст: Помните, что передача данных между клиентом и сервером автоматически создает копии, а работа внутри одного контекста — передает ссылки.

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

← На главную