Передача массива в функцию: по ссылке и по значению

· 9 мин

Содержание

Массив в 1С передаётся в процедуру или функцию по ссылке — это поведение по умолчанию. Параметр внутри метода и переменная снаружи указывают на один и тот же объект, поэтому Добавить, Удалить и изменение элементов видны вызывающему коду. Чтобы отвязать параметр от исходной переменной, перед ним ставят ключевое слово Знач, но с массивом оно работает не так, как ждёт большинство.

Ниже — три рабочих механизма: передача по ссылке, передача по значению и возврат нового массива. Разберём, где Знач защищает данные, а где создаёт иллюзию защиты, как скопировать массив по-настоящему и почему при вызове сервера с клиента массив ведёт себя иначе, чем в обычном методе.

Синтаксис: как объявить массив и параметр

Массив создают конструктором Новый Массив. Параметр функции объявляют обычным именем — тип указывать не нужно, встроенный язык 1С динамически типизирован.

Массив = Новый Массив;
Массив.Добавить("Яблоко");
Массив.Добавить("Груша");

ОбработатьСписок(Массив);

Процедура ОбработатьСписок(СписокТоваров)
    Для Каждого Товар Из СписокТоваров Цикл
        Сообщить(Товар);
    КонецЦикла;
КонецПроцедуры

Имя параметра (СписокТоваров) не обязано совпадать с именем переменной (Массив) — связь идёт по позиции в вызове, а не по названию.

Конструктор принимает размер и размерности:

  • Новый Массив — пустой массив.
  • Новый Массив(10) — 10 элементов со значением Неопределено.
  • Новый Массив(3, 4) — двумерный массив 3×4 (о многомерных ниже).

Индексация начинается с нуля: первый элемент — Массив[0], последний — Массив[Массив.ВГраница()], а ВГраница() возвращает Количество() - 1.

Передача по ссылке: изменения видны снаружи

Без ключевых слов параметр и аргумент — это одна и та же область памяти. Любая операция, меняющая содержимое массива внутри метода, отражается на исходной переменной.

Процедура ДобавитьПозицию(Список)
    Список.Добавить("Новая строка");
КонецПроцедуры

Данные = Новый Массив;
Данные.Добавить("Первая");

ДобавитьПозицию(Данные);

Сообщить(Данные.Количество()); // 2

Это удобно, когда функция наполняет коллекцию: передали пустой массив, метод заполнил его, вызывающий код получил результат без явного Возврат.

Если метод должен только читать массив, но не менять его, договоритесь об этом на уровне кода: передавайте по значению или используйте фиксированный массив. Полагаться на то, что «я же не буду менять», в большом проекте опасно.

Передача по ссылке касается и переприсваивания. Если внутри метода параметру присвоить новый массив, снаружи переменная тоже начнёт указывать на него:

Процедура ПересоздатьСписок(Список)
    Список = Новый Массив;  // снаружи переменная получит ПУСТОЙ массив
    Список.Добавить("Только это");
КонецПроцедуры

После вызова исходная переменная будет содержать один элемент "Только это" — старое содержимое потеряно.

Передача по значению и ключевое слово Знач

Ключевое слово Знач перед параметром отключает передачу по ссылке:

Процедура Посчитать(Знач Количество)
    Количество = Количество * 2;  // снаружи НЕ изменится
КонецПроцедуры

Для примитивов (число, строка, дата, булево) Знач работает очевидно: создаётся копия значения, и внутренние изменения не выходят наружу.

С массивом поведение другое, и это главный подвох темы. Знач копирует ссылку, а не сам массив. Параметр получает отдельную ссылку, но она указывает на тот же объект. Поэтому:

Процедура Пример(Знач Список)
    Список.Добавить("X");   // ВИДНО снаружи — объект тот же
    Список = Новый Массив;  // НЕ видно снаружи — переприсвоили локальную ссылку
    Список.Добавить("Y");
КонецПроцедуры

Данные = Новый Массив;
Пример(Данные);

Сообщить(Данные.Количество()); // 1, в массиве только "X"

Разложим, что произошло:

  1. Добавить("X") изменил содержимое общего объекта — снаружи видно.
  2. Список = Новый Массив переназначил локальную копию ссылки — снаружи не видно.
  3. Добавить("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.
Проверьте себя
Процедура с параметром `Знач Список` вызвала `Список.Добавить("X")`. Отразится ли это на исходном массиве снаружи?
Как сделать массив независимой копией вместе с вложенными объектами?
Что произойдёт с массивом при вызове `&НаСервереБезКонтекста` метода с клиента?

Частые вопросы

Чем отличается передача в процедуру и в функцию?
Механизм одинаков: и процедура, и функция получают массив по ссылке по умолчанию. Разница только в том, что функция может вернуть значение оператором Возврат, а процедура — нет.
Нужно ли указывать тип параметра-массива?
Нет. Встроенный язык 1С динамически типизирован, тип в объявлении параметра не пишут. При необходимости проверяйте тип внутри метода через ТипЗнч().
Как передать массив, чтобы метод точно не изменил его?
Передайте поверхностную или полную копию, либо оберните в Новый ФиксированныйМассив(...) — у фиксированного массива нет методов изменения, и случайная правка вызовет ошибку.
Можно ли передать в функцию часть массива?
Отдельного среза в 1С нет. Соберите новый массив с нужными элементами (цикл Для Каждого с условием) и передайте его.
Почему изменения массива на сервере не видны на клиенте?
На границе клиент-сервер массив сериализуется и передаётся копией. Чтобы получить изменения, верните массив из серверной функции и присвойте результат переменной на клиенте.