В практике разработки на платформе «1С:Предприятие 8» программисты часто сталкиваются с ситуацией, когда при заполнении массива структурами в цикле все элементы результирующей коллекции внезапно оказываются одинаковыми. Они дублируют значение самой последней итерации. Давайте подробно разберем, почему возникает эта проблема, как устроена работа с памятью в 1С, и рассмотрим правильные архитектурные подходы для ее решения.
Для того чтобы понять суть проблемы, нам необходимо заглянуть «под капот» платформы и проанализировать, как 1С работает с переменными разных типов. Все типы данных в системе можно разделить на две большие категории: типы значений и ссылочные типы.
К типам значений (простым типам) относятся такие типы, как Число, Строка, Булево и Дата. Когда мы присваиваем значение одной такой переменной другой или передаем ее в параметры, платформа копирует само значение. В памяти создаются две абсолютно независимые ячейки. Изменение одной переменной никак не влияет на другую.
К ссылочным типам (объектам и универсальным коллекциям) относятся такие типы данных, как Структура, Соответствие, Массив, ТаблицаЗначений, а также ссылки на элементы справочников и документов (например, СправочникСсылка.Номенклатура). Переменная такого типа хранит в себе не сами данные, а указатель (адрес в оперативной памяти) на место, где этот объект расположен.
Проанализируем исходный ошибочный код, с которым столкнулся автор темы:
ШкСтрока = Новый Структура; // (1) Создается ОДИН объект в памяти
//Получаем штрихкоды товара
ШтрхкодыТовара = ПолучитьШтрихкодыТовара(ВыборкаДетальныеЗаписи.Ссылка);
Если ШтрхкодыТовара.Количество() > 0 Тогда
Для Каждого шк Из ШтрхкодыТовара Цикл
ШкСтрока.Вставить("code", ВыборкаДетальныеЗаписи.НоменклатураКод); // (2) Запись в тот же объект
ШкСтрока.Вставить("barcode", шк); // (3) Перезапись ключа в том же объекте
шк = Неопределено; // (4) Бесполезная очистка локальной переменной цикла
Штрихкоды.Добавить(ШкСтрока); // (5) Добавление ССЫЛКИ на один и тот же объект в массив
КонецЦикла;
КонецЕсли;
Давайте пошагово разберем, что происходит в оперативной памяти компьютера при выполнении этого алгоритма:
ШкСтрока = Новый Структура; инициализирует в памяти ровно один экземпляр структуры. Переменная ШкСтрока теперь хранит адрес этой области памяти (например, условный адрес 0x001).code и barcode.Штрихкоды.Добавить(ШкСтрока) помещает в массив не копию структуры, а копию адреса памяти (0x001). То есть первый элемент массива теперь указывает на адрес 0x001.Штрихкоды.Добавить(ШкСтрока) снова добавляет в массив адрес 0x001. Теперь и первый, и второй элементы массива ссылаются на один и тот же физический объект в памяти!шк = Неопределено; не приносит результата, поскольку переменная шк является лишь итератором цикла, содержащим простое значение типа Строка (текущий штрихкод), и никакого отношения к адресу структуры ШкСтрока не имеет.В результате работы такого цикла мы получаем массив, заполненный множеством ссылок, каждая из которых указывает на одну и ту же область памяти. Разумеется, при обращении к любому элементу этого массива мы будем видеть данные последней итерации цикла, когда свойства этой несчастной структуры были перезаписаны в последний раз.
Для того чтобы исправить данное поведение, нам необходимо добиться создания нового физического объекта структуры на каждом шаге цикла. Рассмотрим наиболее лаконичный, производительный и изящный способ реализации этой задачи.
Мы можем использовать конструктор структуры, который позволяет инициализировать объект и сразу заполнять его свойствами. Напишем код правильно:
ШтрхкодыТовара = ПолучитьШтрихкодыТовара(ВыборкаДетальныеЗаписи.Ссылка);
Для Каждого шк Из ШтрхкодыТовара Цикл
// На каждой итерации оператор Новый создает физически новый объект в памяти
НоваяСтрокаШК = Новый Структура("code, barcode", ВыборкаДетальныеЗаписи.НоменклатураКод, шк);
Штрихкоды.Добавить(НоваяСтрокаШК);
КонецЦикла;
Посмотрим, какие важные изменения мы внесли в алгоритм:
Новый. Он принудительно выделяет новую область памяти под объект на каждой итерации (например, 0x001 на первом шаге, 0x002 на втором и так далее).Если ШтрхкодыТовара.Количество() > 0 Тогда. В платформе 1С цикл Для Каждого устроен таким образом, что если коллекция пуста, тело цикла просто ни разу не выполнится. Лишнее условие только загромождает код и снижает его читаемость.Новый Структура("ключ1, ключ2", значение1, значение2) позволяет сократить код, избегая вызова методов Вставить() на отдельных строках.Очень часто массивы структур используются разработчиками для последующего поиска данных. Например, чтобы по штрихкоду быстро находить код номенклатуры. Если ваша задача состоит именно в этом, то использование связки «Массив + Структуры» является неоптимальным с точки зрения производительности.
При поиске в массиве платформе приходится совершать полный перебор всех его элементов (линейный поиск с транзакционной сложностью O(N)). Если элементов тысячи, это может существенно замедлить работу системы. В таких случаях гораздо правильнее использовать специализированную коллекцию — Соответствие.
Рассмотрим пример создания и использования соответствия штрихкодов:
// Создаем коллекцию для быстрого поиска ключей
СоответствиеШтрихкодов = Новый Соответствие;
ШтрхкодыТовара = ПолучитьШтрихкодыТовара(ВыборкаДетальныеЗаписи.Ссылка);
Для Каждого шк Из ШтрхкодыТовара Цикл
// Ключом будет выступать уникальный штрихкод, значением — код номенклатуры
СоответствиеШтрихкодов.Вставить(шк, ВыборкаДетальныеЗаписи.НоменклатураКод);
КонецЦикла;
// Пример быстрого получения кода номенклатуры по штрихкоду без перебора коллекции:
ИскомыйШтрихкод = "4601234567890";
КодНоменклатуры = СоответствиеШтрихкодов[ИскомыйШтрихкод];
Поиск в объекте Соответствие работает по принципу хеш-таблицы (сложность поиска O(1)), что обеспечивает практически мгновенный доступ к значению по ключу независимо от размера коллекции. Это делает ваш код не только чистым, но и чрезвычайно быстрым.
В ходе обсуждения на форумах иногда можно встретить экзотические советы. Например, в Сообщении 6 было предложено клонировать структуру путем её сериализации во внутреннюю строку 1С и последующей десериализации:
Штрихкоды.Добавить(ЗначениеИзСтрокиВнутр(ЗначениеВСтрокуВнутр(ШкСтрока)));
Этот прием действительно позволяет создать глубокую физическую копию объекта в памяти, но в современной профессиональной разработке использовать его категорически запрещено по следующим причинам:
ЗначениеВСтрокуВнутр и ЗначениеИзСтрокиВнутр являются системными и предназначены исключительно для внутренних нужд платформы. Их использование в прикладном коде считается грубым нарушением стандартов качества.Что же делать, если перед нами действительно стоит задача скопировать существующую структуру (создать ее клон в памяти), не прибегая к объявлению её свойств заново? Рассмотрим три легитимных и безопасных способа.
Для простых структур, не содержащих внутри себя вложенных таблиц или массивов, идеально подходит встроенный метод платформы ЗаполнитьЗначенияСвойств. Этот метод работает на уровне платформы и выполняет операцию копирования максимально быстро:
// Предположим, у нас есть исходная структура
ИсходнаяСтруктура = Новый Структура("Товар, Количество, Цена", СсылкаНоменклатуры, 10, 150);
// Создаем новую пустую структуру
КопияСтруктуры = Новый Структура;
// Быстро копируем все свойства из исходной структуры в новую
ЗаполнитьЗначенияСвойств(КопияСтруктуры, ИсходнаяСтруктура);
Существует очень интересный и быстрый способ клонировать структуру буквально в одну строку, используя особенности работы конструктора фиксированных коллекций:
// Создаем точную копию структуры в одну строку
КопияСтруктуры = Новый Структура(Новый ФиксированнаяСтруктура(ИсходнаяСтруктура));
Давайте проанализируем, как это работает. При передаче обычной структуры в конструктор ФиксированнаяСтруктура, система создает в памяти её неизменяемый клон. Затем мы передаем эту фиксированную структуру обратно в конструктор обычной Структуры, получая полноценный, готовый к изменению независимый дубликат исходного объекта.
Если в вашей конфигурации установлена БСП (а она присутствует практически во всех типовых решениях от 1С: ERP, УТ, КА, БП, ЗУП), вы можете использовать готовые и протестированные методы общего назначения для глубокого рекурсивного копирования любых коллекций:
// Копирование на стороне сервера
КопияСтруктуры = ОбщегоНазначения.СкопироватьРекурсивно(ИсходнаяСтруктура);
// Копирование на стороне тонкого или толстого клиента
КопияСтруктуры = ОбщегоНазначенияКлиент.СкопироватьРекурсивно(ИсходнаяСтруктура);
Этот метод без проблем справится со структурами любой степени вложенности, внутри которых могут находиться массивы, таблицы значений или другие соответствия, создав для каждого уровня вложенности новые независимые области памяти.
В завершение разберем важный стандарт оформления кода «1С», касающийся работы со структурами:
Правило ограничения параметров конструктора: Официальные стандарты разработки не рекомендуют передавать в конструктор Новый Структура(...) более трех свойств на одной строке, так как это снижает читаемость кода при горизонтальной прокрутке. Если вам необходимо создать структуру с большим количеством полей, правильнее сначала объявить ее пустой, а затем последовательно заполнять с помощью метода Вставить():
// Рекомендуемый шаблон для создания структур с большим количеством полей:
ПараметрыЗапроса = Новый Структура;
ПараметрыЗапроса.Вставить("Организация", УказыватьОрганизацию);
ПараметрыЗапроса.Вставить("Подразделение", УказыватьПодразделение);
ПараметрыЗапроса.Вставить("НачалоПериода", ПериодРегистрации);
ПараметрыЗапроса.Вставить("КонецПериода", КонецПериода);
ПараметрыЗапроса.Вставить("РежимПроведения",РежимПроведения);
Для нашего же случая (где полей всего два: code и barcode) лаконичная запись в одну строку Новый Структура("code, barcode", Код, Штрихкод) полностью соответствует стандартам и является лучшим выбором.