Передача массива в функцию: по ссылке и по значению
Содержание
Массив в 1С передаётся в процедуру или функцию по ссылке — это поведение по умолчанию. Параметр внутри метода и переменная снаружи указывают на один и тот же объект, поэтому Добавить, Удалить и изменение элементов видны вызывающему коду. Чтобы отвязать параметр от исходной переменной, перед ним ставят ключевое слово Знач, но с массивом оно работает не так, как ждёт большинство.
Ниже — три рабочих механизма: передача по ссылке, передача по значению и возврат нового массива. Разберём, где Знач защищает данные, а где создаёт иллюзию защиты, как скопировать массив по-настоящему и почему при вызове сервера с клиента массив ведёт себя иначе, чем в обычном методе.
Синтаксис: как объявить массив и параметр
Массив создают конструктором Новый Массив. Параметр функции объявляют обычным именем — тип указывать не нужно, встроенный язык 1С динамически типизирован.
Массив = Новый Массив;
Массив.Добавить("Яблоко");
Массив.Добавить("Груша");
ОбработатьСписок(Массив);
Процедура ОбработатьСписок(СписокТоваров)
Для Каждого Товар Из СписокТоваров Цикл
Сообщить(Товар);
КонецЦикла;
КонецПроцедуры
Имя параметра (СписокТоваров) не обязано совпадать с именем переменной (Массив) — связь идёт по позиции в вызове, а не по названию.
Конструктор принимает размер и размерности:
Новый Массив— пустой массив.Новый Массив(10)— 10 элементов со значениемНеопределено.Новый Массив(3, 4)— двумерный массив 3×4 (о многомерных ниже).
Индексация начинается с нуля: первый элемент — Массив[0], последний — Массив[Массив.ВГраница()], а ВГраница() возвращает Количество() - 1.
Передача по ссылке: изменения видны снаружи
Без ключевых слов параметр и аргумент — это одна и та же область памяти. Любая операция, меняющая содержимое массива внутри метода, отражается на исходной переменной.
Процедура ДобавитьПозицию(Список)
Список.Добавить("Новая строка");
КонецПроцедуры
Данные = Новый Массив;
Данные.Добавить("Первая");
ДобавитьПозицию(Данные);
Сообщить(Данные.Количество()); // 2
Это удобно, когда функция наполняет коллекцию: передали пустой массив, метод заполнил его, вызывающий код получил результат без явного Возврат.
Если метод должен только читать массив, но не менять его, договоритесь об этом на уровне кода: передавайте по значению или используйте фиксированный массив. Полагаться на то, что «я же не буду менять», в большом проекте опасно.
Передача по ссылке касается и переприсваивания. Если внутри метода параметру присвоить новый массив, снаружи переменная тоже начнёт указывать на него:
Процедура ПересоздатьСписок(Список)
Список = Новый Массив; // снаружи переменная получит ПУСТОЙ массив
Список.Добавить("Только это");
КонецПроцедуры
После вызова исходная переменная будет содержать один элемент "Только это" — старое содержимое потеряно.
Передача по значению и ключевое слово Знач
Ключевое слово Знач перед параметром отключает передачу по ссылке:
Процедура Посчитать(Знач Количество)
Количество = Количество * 2; // снаружи НЕ изменится
КонецПроцедуры
Для примитивов (число, строка, дата, булево) Знач работает очевидно: создаётся копия значения, и внутренние изменения не выходят наружу.
С массивом поведение другое, и это главный подвох темы. Знач копирует ссылку, а не сам массив. Параметр получает отдельную ссылку, но она указывает на тот же объект. Поэтому:
Процедура Пример(Знач Список)
Список.Добавить("X"); // ВИДНО снаружи — объект тот же
Список = Новый Массив; // НЕ видно снаружи — переприсвоили локальную ссылку
Список.Добавить("Y");
КонецПроцедуры
Данные = Новый Массив;
Пример(Данные);
Сообщить(Данные.Количество()); // 1, в массиве только "X"
Разложим, что произошло:
Добавить("X")изменил содержимое общего объекта — снаружи видно.Список = Новый Массивпереназначил локальную копию ссылки — снаружи не видно.Добавить("Y")ушёл уже в локальный массив, который исчезнет после выхода из метода.
Вывод: Знач для массива защищает только от переприсваивания параметра, но не от изменения элементов. Если нужно гарантированно уберечь исходные данные — делайте копию.
| Что делаем с массивом внутри метода | Без Знач | С Знач |
|---|---|---|
Добавить / Удалить элемент | видно снаружи | видно снаружи |
| Изменить элемент по индексу | видно снаружи | видно снаружи |
Переприсвоить параметр (= Новый Массив) | видно снаружи | не видно |
Как сделать настоящую копию массива
Чтобы метод работал с данными, не трогая оригинал, передайте ему копию.
Поверхностная копия — новый массив с теми же элементами. Подходит, когда элементы примитивны (строки, числа):
Функция КопияМассива(Исходный)
Копия = Новый Массив;
Для Каждого Элемент Из Исходный Цикл
Копия.Добавить(Элемент);
КонецЦикла;
Возврат Копия;
КонецФункции
Поверхностная копия не спасёт, если элементы — тоже коллекции (массивы, структуры): ссылки на вложенные объекты останутся общими.
Полная копия через сериализацию — отвязывает и вложенные объекты:
ПолнаяКопия = ЗначениеИзСтрокиВнутр(ЗначениеВСтрокуВнутр(Массив));
ЗначениеВСтрокуВнутр превращает массив во внутреннее строковое представление, а ЗначениеИзСтрокиВнутр собирает из строки новый, полностью независимый объект. Способ работает для массивов из сериализуемых значений: примитивов, структур, соответствий, ссылок на объекты базы.
Полную копию не делайте на «горячих» участках в цикле по тысячам элементов — сериализация заметно медленнее ручного копирования. Для плоского массива примитивов хватает цикла Для Каждого.
То же правило передачи по ссылке действует и для других коллекций — Структура, Соответствие, СписокЗначений, ТаблицаЗначений. Все они объектного типа, поэтому Знач для них тоже копирует лишь ссылку, а не содержимое. Если метод принимает структуру и меняет её поля, снаружи это будет видно даже с Знач. Логика копирования — та же: ручной обход или сериализация.
Фиксированный и многомерный массив как параметр
Фиксированный массив (ФиксированныйМассив) — неизменяемая обёртка над массивом. Если метод должен только читать коллекцию, передайте фиксированный массив: попытка вызвать Добавить вызовет ошибку, и вы сразу увидите неверное обращение.
Исходный = Новый Массив;
Исходный.Добавить(1);
Исходный.Добавить(2);
ФиксМассив = Новый ФиксированныйМассив(Исходный);
ТолькоЧитаем(ФиксМассив); // внутри нельзя Добавить/Удалить
Это и есть «типизация по доступу»: тип параметра прямо говорит, что менять коллекцию нельзя.
Многомерный массив передаётся так же, как обычный, — по ссылке. Обращение к элементам идёт через вложенные индексы:
Матрица = Новый Массив(3, 4); // 3 строки, 4 столбца
Матрица[0][0] = "A";
Матрица[2][3] = "B";
ОбработатьМатрицу(Матрица);
Процедура ОбработатьМатрицу(Данные)
Для Строка = 0 По Данные.ВГраница() Цикл
Для Столбец = 0 По Данные[Строка].ВГраница() Цикл
Сообщить(Данные[Строка][Столбец]);
КонецЦикла;
КонецЦикла;
КонецПроцедуры
Про «типизацию элементов» важно знать честно: массив 1С не ограничивает тип элементов — в одном массиве могут лежать число, строка и ссылка одновременно. Если нужно гарантировать однородность, проверяйте тип при добавлении через ТипЗнч(Элемент) или валидируйте массив на входе в метод.
Возврат массива из функции
Функция возвращает массив оператором Возврат. Возвращается ссылка на объект — отдельного копирования 1С не делает.
Функция ОтобратьЧётные(Исходный)
Результат = Новый Массив;
Для Каждого Элемент Из Исходный Цикл
Если Элемент % 2 = 0 Тогда
Результат.Добавить(Элемент);
КонецЕсли;
КонецЦикла;
Возврат Результат;
КонецФункции
Числа = Новый Массив;
Числа.Добавить(1);
Числа.Добавить(2);
Числа.Добавить(4);
Чётные = ОтобратьЧётные(Числа);
Сообщить(Чётные.Количество()); // 2
Оператор % — остаток от деления, им удобно проверять чётность. Исходный массив Числа функция не трогает — она собирает новый Результат, поэтому побочных эффектов нет.
Шаблон «принять исходные данные, вернуть новый массив» чище, чем «заполнить переданный по ссылке». Вызывающий код видит по сигнатуре: на входе — источник, на выходе — результат, оригинал цел.
Передача массива между клиентом и сервером
Отдельная история — вызов серверного метода из клиентского кода управляемой формы. Здесь правило «по ссылке» не действует.
При переходе с клиента на сервер (&НаСервереБезКонтекста, вызов внеконтекстной процедуры) массив сериализуется и передаётся по значению. Серверный код получает копию. Изменения, сделанные на сервере, на клиент сами не вернутся.
&НаКлиенте
Процедура ВыполнитьОбработку(Команда)
Список = Новый Массив;
Список.Добавить("A");
ДополнитьНаСервере(Список); // на сервере копия
Сообщить(Список.Количество()); // по-прежнему 1!
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ДополнитьНаСервере(Список)
Список.Добавить("B"); // меняется серверная КОПИЯ
КонецПроцедуры
Чтобы получить изменения на клиенте, верните массив из серверной функции:
&НаКлиенте
Процедура ВыполнитьОбработку(Команда)
Список = Новый Массив;
Список.Добавить("A");
Список = ДополнитьНаСервере(Список); // приняли результат обратно
Сообщить(Список.Количество()); // 2
КонецПроцедуры
&НаСервереБезКонтекста
Функция ДополнитьНаСервере(Список)
Список.Добавить("B");
Возврат Список;
КонецФункции
В серверный метод можно передавать только сериализуемые значения. Массив ссылок на объекты базы пройдёт, а массив, где лежит, например, ТаблицаЗначений с несериализуемым содержимым или прикладной объект вроде открытой формы — вызовет ошибку передачи.
Оптимизация, запросы и частые ошибки
Передача по ссылке — дешёвая операция: 1С не копирует массив при вызове метода, передаётся только ссылка. Поэтому не бойтесь гонять большие массивы между процедурами одного модуля — накладных расходов на это нет. Дорого стоит другое:
- Полная копия через сериализацию большого массива — сотни тысяч элементов превратятся в строку и обратно.
- Передача массива на сервер — он сериализуется целиком. Если на клиенте массив на 100 000 ссылок, подумайте, нельзя ли отобрать нужное прямо на сервере.
- Избыточный
Значтам, где метод и так не меняет данные, — лишняя копия ссылки роли не играет, но копирование содержимого «на всякий случай» бьёт по памяти.
Частый практический сценарий — передать массив в параметр запроса, чтобы отобрать данные по списку. Массив ссылок или примитивов кладут в параметр и используют в условии В:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ Ссылка ИЗ Справочник.Номенклатура
|ГДЕ Ссылка В (&СписокТоваров)";
Запрос.УстановитьПараметр("СписокТоваров", МассивСсылок);
Результат = Запрос.Выполнить();
Внутри скобок В (&СписокТоваров) платформа сама разворачивает массив в список значений. Вручную перечислять элементы не нужно — передаёте массив целиком.
Для условия В подойдёт и массив, и список значений, и даже результат другого запроса. Массив — самый простой вариант, когда список собран кодом.
Теперь три промаха, на которых спотыкаются чаще всего.
Ждут, что Знач защитит содержимое массива. Не защитит — см. раздел про Знач. Если данные менять нельзя, передавайте копию или ФиксированныйМассив.
Удаляют элементы внутри Для Каждого. Изменение коллекции во время обхода Для Каждого даёт непредсказуемый результат. Удаляйте в обратном порядке циклом Пока:
Индекс = Массив.Количество() - 1;
Пока Индекс >= 0 Цикл
Если НужноУдалить(Массив[Индекс]) Тогда
Массив.Удалить(Индекс);
КонецЕсли;
Индекс = Индекс - 1;
КонецЦикла;
Идём от последнего индекса к нулю — после удаления сдвигаются только уже пройденные позиции, обход не ломается.
Передают массив на сервер и ждут изменений обратно. На границе клиент-сервер массив копируется. Возвращайте результат из функции.
Что ещё помогает держать код чистым:
- 📌 Если метод только читает — передавайте
ФиксированныйМассивили по значению копию. - 📌 Если метод наполняет коллекцию — лучше возвращать новый массив, чем менять переданный по ссылке.
- 📌 Имя параметра делайте говорящим:
СписокТоваров,Строки, а неМассив1.
Частые вопросы
Чем отличается передача в процедуру и в функцию?
Возврат, а процедура — нет.Нужно ли указывать тип параметра-массива?
ТипЗнч().Как передать массив, чтобы метод точно не изменил его?
Новый ФиксированныйМассив(...) — у фиксированного массива нет методов изменения, и случайная правка вызовет ошибку.Можно ли передать в функцию часть массива?
Для Каждого с условием) и передайте его.