Как ведут себя изменяемые значения во встроенном языке 1С
Содержание
Мутабельные значения в 1С — это значения, содержимое которых можно изменить после создания: Массив, Структура, Соответствие, ТаблицаЗначений, СписокЗначений, объекты справочников и документов. Неизменяемые (иммутабельные) — Строка, Число, Дата, Булево, ссылки на объекты базы и фиксированные коллекции: их нельзя поменять, можно только получить новое значение.
Разница проявляется в одном месте: когда две переменные или процедура и вызывающий код работают с одним и тем же экземпляром. Если тип мутабельный, изменение через одну переменную видно через другую. Если неизменяемый — каждое «изменение» создаёт новое значение, а старое остаётся прежним. Это правило платформы 1С:Предприятие 8.3, от конфигурации оно не зависит.
Изменяемые и неизменяемые значения: в чём суть
Переменная во встроенном языке 1С хранит не сам массив или структуру, а указатель на экземпляр в памяти. Присваивание Б = А копирует указатель, а не содержимое. После этого А и Б смотрят на один объект.
Для неизменяемых типов это ничего не значит. У строки нет метода, который поменял бы её на месте. СтрЗаменить(), ВРег(), Лев() возвращают новую строку. Исходная не трогается.
Для мутабельных типов это значит всё:
- 🔁
Б.Добавить(5)добавит элемент и вА— это один массив; - 🧩
Б.Вставить("Ключ", 1)появится и в исходной структуре; - 📄
Б.Удалить(0)удалит строку из таблицы, на которую смотрят обе переменные.
Отсюда простое правило: мутабельное значение меняют методы самого значения (Добавить, Вставить, Удалить, Очистить, присваивание свойству). Присваивание переменной (Б = Новый Массив) ничего не меняет в старом экземпляре — переменная просто начинает указывать на другой.
А = Новый Массив;
А.Добавить(1);
Б = А; // одна и та же коллекция
Б.Добавить(2); // А.Количество() = 2
Б = Новый Массив; // Б теперь новый массив
Б.Добавить(3); // А не изменился: в нём по-прежнему 1 и 2
Как 1С передаёт значения: ссылка, копия и Знач
В процедуры и функции 1С параметры по умолчанию передаются по ссылке. Это касается любых типов, а не только коллекций. Модификатор Знач переключает передачу на копию переменной.
Здесь путаются даже опытные разработчики. Знач копирует переменную, но не содержимое объекта, на который она указывает. Для мутабельного типа копия переменной смотрит на тот же экземпляр.
Что меняет Знач, а что нет
Процедура Переназначить(Список)
Список = Новый Массив; // вызывающий код получит пустой массив
КонецПроцедуры
Процедура ПереназначитьЗнач(Знач Список)
Список = Новый Массив; // вызывающий код ничего не заметит
КонецПроцедуры
Процедура ДобавитьЗнач(Знач Список)
Список.Добавить(99); // элемент появится у вызывающего!
КонецПроцедуры
| Действие внутри процедуры | Без Знач | С Знач |
|---|---|---|
Параметр = Новое значение | меняется переменная вызывающего | не видно снаружи |
Параметр.Добавить(...) для массива | видно снаружи | тоже видно снаружи |
Параметр = Параметр + 1 для числа | меняется переменная вызывающего | не видно снаружи |
Параметр = СтрЗаменить(...) для строки | меняется переменная вызывающего | не видно снаружи |
Знач не защищает массив, структуру или таблицу значений от изменений. Если процедура не должна портить переданную коллекцию, передавайте копию или фиксированную коллекцию.
Вызов сервера: значения копируются
Между клиентом и сервером значения сериализуются. На сервер приходит копия массива или структуры, а не исходный экземпляр с клиента.
Параметры без Знач платформа после вызова передаёт обратно, и клиент видит изменения. С Знач обратной передачи нет: сервер может менять полученную коллекцию как угодно, на клиенте она останется прежней. Поэтому в серверных процедурах Знач ставят ради трафика: не нужно гнать данные назад.
Какие типы в 1С мутабельные
Удобно делить типы на три группы.
Изменяемые универсальные коллекции
- 📦
Массив—Добавить,Вставить,Установить,Удалить,Очистить; - 🗂
Структура—Вставить,Удалить, присваивание свойствуСтруктура.Ключ = ...; - 🔑
Соответствие—Вставить,Удалить, присваивание по ключуСоотв[Ключ] = ...; - 📋
СписокЗначений,ТаблицаЗначений,ДеревоЗначений— добавление и удаление строк, изменение колонок и ячеек.
Объекты прикладных данных
СправочникОбъект, ДокументОбъект, наборы записей регистров, ДанныеФормыСтруктура и коллекции данных формы. Их меняют, а потом записывают в базу методом Записать().
Ссылка на элемент (СправочникСсылка, ДокументСсылка) при этом неизменяема. Через ссылку можно прочитать реквизит, но нельзя его поменять. Для изменения вызывают Ссылка.ПолучитьОбъект().
Каждый вызов ПолучитьОбъект() создаёт новый экземпляр объекта. Два вызова дадут два независимых объекта: правки в одном не видны в другом до записи.
Неизменяемые значения
- 🔤
Строка,Число,Дата,Булево,Неопределено,Null; - 🔗 ссылки на объекты базы,
УникальныйИдентификатор,Тип,ОписаниеТипов; - 🔒
ФиксированныйМассив,ФиксированнаяСтруктура,ФиксированноеСоответствие.
У ОписаниеТипов тоже нет методов изменения. Чтобы добавить тип, создают новое описание: Новый ОписаниеТипов(Исходное, "Число").
Примеры изменения коллекций и объектов
Функция, которая «случайно» портит параметр
Типичная ошибка: функция сортирует или фильтрует переданную таблицу, а вызывающий код потом работает с испорченными данными.
Функция СуммаПоПервымТрём(Таблица)
Таблица.Сортировать("Сумма Убыв"); // меняет исходную таблицу!
Итог = 0;
Для Индекс = 0 По Мин(2, Таблица.Количество() - 1) Цикл
Итог = Итог + Таблица[Индекс].Сумма;
КонецЦикла;
Возврат Итог;
КонецФункции
После вызова строки исходной таблицы окажутся отсортированы. Исправление — работать с копией:
Копия = Таблица.Скопировать();
Копия.Сортировать("Сумма Убыв");
Удаление элементов в цикле
Массив меняется на месте, поэтому удалять из него элементы прямым циклом нельзя: индексы сдвигаются. Идут с конца:
Количество = Массив.Количество();
Для Сч = 1 По Количество Цикл
Индекс = Количество - Сч;
Если Массив[Индекс] = 0 Тогда
Массив.Удалить(Индекс);
КонецЕсли;
КонецЦикла;
Граница Количество считается один раз до цикла. Удаление элемента с индексом Индекс не сдвигает элементы с меньшими индексами, которые ещё не проверены.
Строка внутри структуры
Неизменяемое значение внутри мутабельной коллекции ведёт себя предсказуемо:
Параметры = Новый Структура("Имя", "Иванов");
Имя = Параметры.Имя;
Имя = Имя + " И.И."; // новая строка в переменной Имя
// Параметры.Имя по-прежнему "Иванов"
Параметры.Имя = Имя; // а вот так структура изменилась
Сравнительная таблица поведения типов
| Тип | Меняется на месте | Б = А, затем изменение Б | Как получить копию |
|---|---|---|---|
Строка, Число, Дата | нет | А не меняется | копия не нужна |
| Ссылка на справочник | нет | А не меняется | копия не нужна |
Массив | да | А меняется | Новый Массив(ФиксированныйМассив) или цикл |
Структура | да | А меняется | цикл Для Каждого с Вставить |
Соответствие | да | А меняется | цикл Для Каждого с Вставить |
ТаблицаЗначений | да | А меняется | Таблица.Скопировать() |
СправочникОбъект | да | А меняется | новый ПолучитьОбъект() или Скопировать() |
ФиксированныйМассив | нет | изменить нельзя | Новый Массив(ФиксированныйМассив) |
ФиксированнаяСтруктура | нет | изменить нельзя | Новый Структура(ФиксированнаяСтруктура) |
Копирование структуры вручную:
Копия = Новый Структура;
Для Каждого КлючИЗначение Из Исходная Цикл
Копия.Вставить(КлючИЗначение.Ключ, КлючИЗначение.Значение);
КонецЦикла;
Все эти способы дают поверхностную копию. Если в структуре лежит массив, в копию попадёт ссылка на тот же массив. Изменение вложенного массива будет видно в обеих структурах.
Для глубокой копии в конфигурациях на БСП есть функция ОбщегоНазначения.СкопироватьРекурсивно(). Она копирует вложенные массивы, структуры, соответствия, списки и таблицы значений.
Мутабельные значения в кэше, параметрах сеанса и общих модулях
Самые неприятные ошибки с мутабельностью возникают там, где один экземпляр живёт дольше одного вызова.
Модули с повторным использованием возвращаемых значений
У общего модуля можно включить свойство «Повторное использование возвращаемых значений»: на время вызова или на время сеанса. Платформа запоминает результат функции и при повторном вызове с теми же параметрами отдаёт его без пересчёта.
Если такая функция возвращает Массив или Структуру, вызывающий код может изменить закэшированное значение. Следующий вызов вернёт уже испорченные данные, и ошибка всплывёт в совсем другом месте программы.
Стандарты разработки 1С поэтому требуют возвращать из таких модулей неизменяемые значения:
Функция КодыСкладов() Экспорт
Коды = Новый Массив;
Коды.Добавить("01");
Коды.Добавить("02");
Возврат Новый ФиксированныйМассив(Коды);
КонецФункции
Если вызывающему коду нужно что-то добавить, он делает свою копию: Мои = Новый Массив(КодыСкладов()).
Параметры сеанса
Параметр сеанса не может иметь тип Массив или Структура. Для набора значений выбирают фиксированные типы, например ФиксированныйМассив или ФиксированнаяСтруктура.
Чтобы изменить такой параметр, его не правят, а заменяют целиком: берут копию, меняют, фиксируют и присваивают заново в серверном коде с нужными правами.
Список = Новый Массив(ПараметрыСеанса.ДоступныеСклады);
Список.Добавить(НовыйСклад);
ПараметрыСеанса.ДоступныеСклады = Новый ФиксированныйМассив(Список);
Фиксация тоже поверхностная
Новый ФиксированнаяСтруктура(Структура) запрещает добавлять и менять ключи. Но если значением ключа был Массив, сам массив остаётся изменяемым. Для надёжной защиты вложенные коллекции фиксируют по отдельности, до фиксации внешней.
Память и скорость: как работать с изменяемыми и неизменяемыми типами
Строки в цикле
Строка неизменяема, поэтому Текст = Текст + Фрагмент в цикле каждый раз создаёт новую строку и копирует в неё всё накопленное. На тысячах итераций это заметно по времени.
Быстрее собрать фрагменты в мутабельный массив и склеить один раз:
Части = Новый Массив;
Для Каждого Строка Из Таблица Цикл
Части.Добавить(Строка.Наименование);
КонецЦикла;
Текст = СтрСоединить(Части, Символы.ПС);
СтрСоединить() есть в платформе 8.3.6 и новее. Для больших текстов подходит и ЗаписьXML или ЗаписьТекста с записью в строку или файл.
Практические правила
- Не копируйте коллекции «на всякий случай». Копия таблицы на 100 000 строк — это вторая таблица в памяти. Копируйте только там, где функция меняет данные, а вызывающему они нужны в исходном виде.
- Если функция меняет параметр намеренно, назовите это в имени:
ЗаполнитьКоды(Массив),ДополнитьСтруктуру(Параметры). Функция с именемПолучить...параметр менять не должна. - Из модулей с кэшем, параметров сеанса и общих настроек отдавайте фиксированные коллекции.
- Для серверных вызовов ставьте
Значу параметров, изменения которых клиенту не нужны: так меньше данных идёт обратно. - Для ключей соответствия используйте неизменяемые значения: строки, числа, ссылки. Мутабельный объект в роли ключа сбивает с толку при поиске.
- Объект справочника или документа получайте один раз и передавайте дальше переменной, а не вызывайте
ПолучитьОбъект()в каждой процедуре. Иначе правки разойдутся по разным экземплярам.
Отлаживать мутабельность удобно в отладчике конфигуратора: поставьте точку останова после вызова процедуры и раскройте коллекцию в окне просмотра значений. Сразу видно, что её содержимое поменялось.
Частые вопросы
Строка в 1С мутабельная или нет?
Можно ли превратить фиксированный массив обратно в обычный?
Новый Массив(ФиксированныйМассив). Получится новый изменяемый массив с теми же элементами. Аналогично работают конструкторы Структура и Соответствие.Почему у фиксированного массива нет метода Добавить?
Добавить() у них даёт ошибку выполнения, а не молчаливое игнорирование.Ссылка на документ — мутабельное значение?
ПолучитьОбъект().Таблица значений внутри структуры копируется вместе со структурой?
ОбщегоНазначения.СкопироватьРекурсивно() в конфигурациях на БСП.