Как передать процедуру или функцию в другой метод на встроенном языке 1С

· 9 мин

Содержание

Передать функцию как параметр в 1С напрямую нельзя: во встроенном языке 1С 8.3 нет типа «функция», лямбд и указателей на метод. Строка Обработать(Массив, Удвоить) не скомпилируется, а Удвоить() в скобках вызовет функцию сразу и передаст её результат, а не её саму.

Задача при этом решается штатно. На клиенте в параметр передают объект ОписаниеОповещения, на сервере — имя метода строкой, которое потом вызывают через Выполнить() или Вычислить(). Ниже синтаксис каждого способа, рабочие примеры, ошибки и то, как их ловить.

Синтаксис передачи функции в параметре: три рабочих варианта

Любой из способов сводится к одному: в параметр уходит не код, а «адрес» метода — имя и модуль, где он лежит.

СпособГде работаетЧто передаёмВозвращает значение
ОписаниеОповещенияклиент (формы, общие клиентские модули)имя процедуры + модуль + доп. параметрынет, результат приходит параметром
Имя метода + Вычислить()сервер, клиентстроку вида "Модуль.Функция"да
Имя метода + Выполнить()сервер, клиентстроку с вызовом процедурынет
Модуль как значение + имясерверссылку на общий модуль и имя методазависит от вызова

ОписаниеОповещения

Конструктор принимает имя процедуры, модуль и необязательные дополнительные параметры:

Оповещение = Новый ОписаниеОповещения("ИмяПроцедуры", ЭтотОбъект, ДополнительныеПараметры);

Вызывающий метод получает этот объект как обычный параметр и в нужный момент запускает его:

ВыполнитьОбработкуОповещения(Оповещение, Результат);

Процедура-обработчик всегда имеет два параметра: результат и дополнительные параметры.

&НаКлиенте
Процедура ИмяПроцедуры(Результат, ДополнительныеПараметры) Экспорт
    // обработка результата
КонецПроцедуры

Имя метода строкой

На сервере функцию передают как строку и вычисляют выражение:

Значение = Вычислить(ИмяФункции + "(Параметр)");

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

Оба метода видят локальные переменные того места, откуда их вызвали. Поэтому в строке можно писать "(Параметр)", а не подставлять значение текстом.

Примеры: передаём функцию в процедуру на практике

Пример 1. Свой диалог с «колбэком» на клиенте

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

// Модуль формы
&НаКлиенте
Процедура УдалитьСтроки(Команда)
    Параметры = Новый Структура("КоличествоСтрок", Объект.Товары.Количество());
    Оповещение = Новый ОписаниеОповещения("ПослеПодтверждения", ЭтотОбъект, Параметры);
    СпроситьПодтверждение("Удалить все строки табличной части?", Оповещение);
КонецПроцедуры

&НаКлиенте
Процедура СпроситьПодтверждение(ТекстВопроса, ОписаниеЗавершения)
    ПоказатьВопрос(ОписаниеЗавершения, ТекстВопроса, РежимДиалогаВопрос.ДаНет);
КонецПроцедуры

&НаКлиенте
Процедура ПослеПодтверждения(Ответ, ДополнительныеПараметры) Экспорт
    Если Ответ <> КодВозвратаДиалога.Да Тогда
        Возврат;
    КонецЕсли;
    Объект.Товары.Очистить();
    Сообщить(СтрШаблон("Удалено строк: %1", ДополнительныеПараметры.КоличествоСтрок));
КонецПроцедуры

СпроситьПодтверждение ничего не знает о табличной части. Ей передали «что сделать потом», и она это вызвала — ровно то, ради чего ищут передачу функции в параметре.

Пример 2. Своя процедура, которая вызывает переданный обработчик

ПоказатьВопрос сам запускает оповещение. В своём коде делаем это явно:

&НаКлиенте
Процедура ПроверитьЗаполнение(ОписаниеЗавершения)
    Ошибки = Новый Массив;
    Если Не ЗначениеЗаполнено(Объект.Контрагент) Тогда
        Ошибки.Добавить("Не указан контрагент");
    КонецЕсли;
    ВыполнитьОбработкуОповещения(ОписаниеЗавершения, Ошибки);
КонецПроцедуры

Обработчик получит массив ошибок первым параметром.

Пример 3. Функция-преобразователь на сервере

Нужна процедура «применить функцию к каждому элементу массива». Функции-преобразователи лежат в общем серверном модуле ПреобразованияСервер и объявлены с Экспорт.

// Общий модуль ПреобразованияСервер
Функция Удвоить(Число) Экспорт
    Возврат Число * 2;
КонецФункции

Функция ВКвадрат(Число) Экспорт
    Возврат Число * Число;
КонецФункции

Функция Применить(Массив, ИмяФункции) Экспорт
    Результат = Новый Массив;
    Для Каждого Элемент Из Массив Цикл
        Результат.Добавить(Вычислить("ПреобразованияСервер." + ИмяФункции + "(Элемент)"));
    КонецЦикла;
    Возврат Результат;
КонецФункции

Вызов ПреобразованияСервер.Применить(Числа, "ВКвадрат") для массива 1, 2, 3 вернёт 1, 4, 9, а с "Удвоить" — 2, 4, 6.

Контекст выполнения: чем заменить замыкания

В JavaScript или Python функция «помнит» переменные, рядом с которыми её создали. В 1С замыканий нет. Обработчик оповещения запускается позже, и локальных переменных вызывающей процедуры к этому моменту уже не существует.

Контекст передают явно:

  • 📦 Дополнительные параметры — третий аргумент ОписаниеОповещения. Обычно это Структура со всем, что понадобится обработчику: ссылки, номер строки, режим.
  • 🧾 Реквизиты формы — если обработчик живёт в модуле формы, он видит Объект и реквизиты формы на момент вызова.
  • 🔤 Локальный контекст Выполнить()/Вычислить() — строка выполняется там, где написана, поэтому видит переменные текущей процедуры. Но только в момент вызова, а не позже.

Значения, положенные в ДополнительныеПараметры, фиксируются при создании оповещения. Если между созданием и вызовом пользователь поменял данные формы, читайте их из реквизитов, а не из структуры.

Модуль во втором параметре тоже часть контекста. ЭтотОбъект в модуле формы означает «процедура в этой форме». Для общего клиентского модуля передают сам модуль по имени, например ДиалогиКлиент: Новый ОписаниеОповещения("ПослеВыбора", ДиалогиКлиент). Тогда процедура в нём обязана быть экспортной.

Ошибки при передаче функции и как их исправить

Сообщения ниже — то, что выдаёт платформа в таких случаях. Текст в скобках — имя, которое она не нашла.

Диагностика: выберите симптом

Ещё две ошибки проектирования, о которых платформа не предупредит:

  1. Обработчик объявлен как функция и от него ждут результат. ВыполнитьОбработкуОповещения ничего не возвращает. Результат обработчик должен записать в реквизит формы или передать дальше следующим оповещением.
  2. Имя метода приходит извне. Если строка для Выполнить() собирается из данных пользователя, справочника или файла обмена, в неё можно подсунуть любой код. Никогда не выполняйте строку, которую не сформировал ваш собственный код.

Продвинутые техники: цепочки, модуль как значение, БСП

Цепочка оповещений

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

&НаКлиенте
Процедура Шаг1Завершение(Результат, ДополнительныеПараметры) Экспорт
    ДополнительныеПараметры.Вставить("ИмяФайла", Результат);
    Следующий = Новый ОписаниеОповещения("Шаг2Завершение", ЭтотОбъект, ДополнительныеПараметры);
    ЗапуститьШаг2(Следующий);
КонецПроцедуры

В ДополнительныеПараметры удобно хранить ключ ОписаниеЗавершения с оповещением верхнего уровня и вызвать его на последнем шаге.

Модуль как значение

На сервере общий модуль можно положить в переменную и передать как параметр. В конфигурациях на БСП для этого есть функция ОбщегоНазначения.ОбщийМодуль("ИмяМодуля"). Она проверяет, что такой модуль есть в метаданных, и возвращает его.

Модуль = ОбщегоНазначения.ОбщийМодуль("ПреобразованияСервер");
Результат = Модуль.Удвоить(21); // 42

Пара «модуль + имя метода» — самая близкая замена указателю на функцию. Модуль проверен, имя короткое, и вызов Модуль.Удвоить() по-прежнему обычный, без строк.

Вызов по полному имени через БСП

В той же библиотеке есть ОбщегоНазначения.ВыполнитьМетодКонфигурации(ИмяМетода, Параметры). Ей передают полное имя экспортной процедуры ("ПреобразованияСервер.Пересчитать") и массив параметров. Перед выполнением она проверяет, что имя похоже на имя метода, а не на произвольный код. Для чужих имён это безопаснее голого Выполнить().

Фоновые задания

Здесь передача метода строкой — штатный механизм платформы:

Параметры = Новый Массив;
Параметры.Добавить(Организация);
ФоновыеЗадания.Выполнить("ЗакрытиеМесяцаСервер.Рассчитать", Параметры);

Первый аргумент — имя экспортной процедуры общего серверного модуля. Это та же идея: метод передаётся как значение-строка, а вызывает его платформа.

Скорость: во что обходится Выполнить и Вычислить

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

Как ускорить:

  • 🔁 Вынесите выбор метода за цикл. Определите один раз, какую ветку использовать, а в цикле вызывайте функцию напрямую.
  • 🧭 Диспетчер вместо строки. Если вариантов 3–5, обычный Если … ИначеЕсли быстрее и понятнее:
Функция Преобразовать(Число, Вид) Экспорт
    Если Вид = "Удвоить" Тогда
        Возврат Удвоить(Число);
    ИначеЕсли Вид = "ВКвадрат" Тогда
        Возврат ВКвадрат(Число);
    КонецЕсли;
    ВызватьИсключение СтрШаблон("Неизвестный вид преобразования: %1", Вид);
КонецФункции
  • 📦 Передавайте массив целиком. Одна функция, которая принимает массив и возвращает массив, лучше 100 000 отдельных вызовов через строку.
  • 🌐 Не гоняйте оповещения через сервер. Оповещение создаётся и вызывается на клиенте. Серверную часть делайте одним вызовом &НаСервере, а не серией.

Другие подходы без передачи функции

Иногда задачу проще решить, не передавая метод вовсе.

ПодходКогда подходитЧем платим
Диспетчер Если … ИначеЕсливариантов немного и они известны заранеепри новом варианте правим диспетчер
Одинаковый экспортный метод у разных обработок«плагины»: у каждой обработки своя реализация Выполнить()нужна договорённость об имени и параметрах
Оповестить() и ОбработкаОповещениясообщить всем открытым формам, что данные изменилисьшироковещательно, без адресата
Подписки на событияреакция на запись или проведение объектовсрабатывают для всех объектов из списка
Асинх / Ждать (платформа 8.3.18 и новее)цепочка диалогов на клиентеработает не во всех версиях платформы

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

&НаКлиенте
Асинх Процедура УдалитьСтроки(Команда)
    Ответ = Ждать ВопросАсинх("Удалить все строки?", РежимДиалогаВопрос.ДаНет);
    Если Ответ = КодВозвратаДиалога.Да Тогда
        Объект.Товары.Очистить();
    КонецЕсли;
КонецПроцедуры

Если обработчик нужен только для того, чтобы дождаться ответа диалога, этот вариант читается проще. Если же обработчик действительно выбирает вызывающая сторона, ОписаниеОповещения остаётся правильным инструментом.

Отладка: как увидеть, какой метод вызвался

  1. Точка останова в обработчике. Отладчик заходит в процедуру, вызванную через ВыполнитьОбработкуОповещения, Выполнить() или Вычислить(), как в обычную. Ставьте точку прямо в целевом методе, а не на строке вызова.
  2. Проверка оповещения в табло. У объекта ОписаниеОповещения есть свойства ИмяПроцедуры, Модуль и ДополнительныеПараметры. Добавьте переменную в табло и посмотрите, что реально передано.
  3. Перехват ошибки при вызове по строке. Оберните вызов, чтобы видеть имя метода в тексте ошибки:
Попытка
    Значение = Вычислить("ПреобразованияСервер." + ИмяФункции + "(Элемент)");
Исключение
    ВызватьИсключение СтрШаблон("Ошибка вызова %1: %2",
        ИмяФункции, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
  1. Синтаксический контроль не спасёт. Конфигуратор не проверяет имя в строке. Опечатка в "ВКвадрта" всплывёт только во время выполнения. Поэтому имена методов выносите в одну функцию-список или проверяйте по массиву разрешённых значений.

Поиск ссылок на метод в конфигураторе вызовы через строку не находит. Перед удалением или переименованием экспортной функции поищите её имя глобальным поиском по тексту модулей.

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

Можно ли передать функцию в параметре так же, как в JavaScript?
Нет. Во встроенном языке 1С нет типа «функция» и анонимных функций. Передают имя метода строкой или объект ОписаниеОповещения.
Может ли обработчик оповещения вернуть значение?
Нет, ВыполнитьОбработкуОповещения ничего не возвращает. Результат обработчик записывает в реквизит формы или передаёт следующему оповещению.
Нужно ли слово Экспорт у процедуры-обработчика?
Для процедуры в общем модуле — обязательно. Для процедуры в модуле той же формы его обычно ставят тоже: вреда нет, а ошибка «Метод объекта не обнаружен» исключена.
Чем Выполнить отличается от Вычислить?
Вычислить — функция, считает выражение и возвращает значение. Выполнить — оператор, выполняет фрагмент кода, в том числе несколько операторов, и ничего не возвращает.
Безопасно ли вызывать метод по имени из настроек пользователя?
Только после проверки имени по списку разрешённых методов. Иначе через строку можно выполнить произвольный код с правами текущего сеанса.