Как передать процедуру или функцию в другой метод на встроенном языке 1С
Содержание
Передать функцию как параметр в 1С напрямую нельзя: во встроенном языке 1С 8.3 нет типа «функция», лямбд и указателей на метод. Строка Обработать(Массив, Удвоить) не скомпилируется, а Удвоить() в скобках вызовет функцию сразу и передаст её результат, а не её саму.
Задача при этом решается штатно. На клиенте в параметр передают объект ОписаниеОповещения, на сервере — имя метода строкой, которое потом вызывают через Выполнить() или Вычислить(). Ниже синтаксис каждого способа, рабочие примеры, ошибки и то, как их ловить.
Синтаксис передачи функции в параметре: три рабочих варианта
Любой из способов сводится к одному: в параметр уходит не код, а «адрес» метода — имя и модуль, где он лежит.
| Способ | Где работает | Что передаём | Возвращает значение |
|---|---|---|---|
ОписаниеОповещения | клиент (формы, общие клиентские модули) | имя процедуры + модуль + доп. параметры | нет, результат приходит параметром |
Имя метода + Вычислить() | сервер, клиент | строку вида "Модуль.Функция" | да |
Имя метода + Выполнить() | сервер, клиент | строку с вызовом процедуры | нет |
| Модуль как значение + имя | сервер | ссылку на общий модуль и имя метода | зависит от вызова |
ОписаниеОповещения
Конструктор принимает имя процедуры, модуль и необязательные дополнительные параметры:
Оповещение = Новый ОписаниеОповещения("ИмяПроцедуры", ЭтотОбъект, ДополнительныеПараметры);
Вызывающий метод получает этот объект как обычный параметр и в нужный момент запускает его:
ВыполнитьОбработкуОповещения(Оповещение, Результат);
Процедура-обработчик всегда имеет два параметра: результат и дополнительные параметры.
&НаКлиенте
Процедура ИмяПроцедуры(Результат, ДополнительныеПараметры) Экспорт
// обработка результата
КонецПроцедуры
Имя метода строкой
На сервере функцию передают как строку и вычисляют выражение:
Значение = Вычислить(ИмяФункции + "(Параметр)");
Вычислить() возвращает результат выражения, поэтому подходит для функций. Выполнить() — оператор, он ничего не возвращает и годится для процедур или фрагментов кода с присваиванием.
Оба метода видят локальные переменные того места, откуда их вызвали. Поэтому в строке можно писать "(Параметр)", а не подставлять значение текстом.
Примеры: передаём функцию в процедуру на практике
Пример 1. Свой диалог с «колбэком» на клиенте
Типичный случай: общая процедура показывает вопрос, а что делать после ответа, решает вызывающая форма.
// Модуль формы
&НаКлиенте
Процедура УдалитьСтроки(Команда)
Параметры = Новый Структура("КоличествоСтрок", Объект.Товары.Количество());
Оповещение = Новый ОписаниеОповещения("ПослеПодтверждения", ЭтотОбъект, Параметры);
СпроситьПодтверждение("Удалить все строки табличной части?", Оповещение);
КонецПроцедуры
&НаКлиенте
Процедура СпроситьПодтверждение(ТекстВопроса, ОписаниеЗавершения)
ПоказатьВопрос(ОписаниеЗавершения, ТекстВопроса, РежимДиалогаВопрос.ДаНет);
КонецПроцедуры
&НаКлиенте
Процедура ПослеПодтверждения(Ответ, ДополнительныеПараметры) Экспорт
Если Ответ <> КодВозвратаДиалога.Да Тогда
Возврат;
КонецЕсли;
Объект.Товары.Очистить();
Сообщить(СтрШаблон("Удалено строк: %1", ДополнительныеПараметры.КоличествоСтрок));
КонецПроцедуры
СпроситьПодтверждение ничего не знает о табличной части. Ей передали «что сделать потом», и она это вызвала — ровно то, ради чего ищут передачу функции в параметре.
Пример 2. Своя процедура, которая вызывает переданный обработчик
ПоказатьВопрос сам запускает оповещение. В своём коде делаем это явно:
&НаКлиенте
Процедура ПроверитьЗаполнение(ОписаниеЗавершения)
Ошибки = Новый Массив;
Если Не ЗначениеЗаполнено(Объект.Контрагент) Тогда
Ошибки.Добавить("Не указан контрагент");
КонецЕсли;
ВыполнитьОбработкуОповещения(ОписаниеЗавершения, Ошибки);
КонецПроцедуры
Обработчик получит массив ошибок первым параметром.
Пример 3. Функция-преобразователь на сервере
Нужна процедура «применить функцию к каждому элементу массива». Функции-преобразователи лежат в общем серверном модуле ПреобразованияСервер и объявлены с Экспорт.
// Общий модуль ПреобразованияСервер
Функция Удвоить(Число) Экспорт
Возврат Число * 2;
КонецФункции
Функция ВКвадрат(Число) Экспорт
Возврат Число * Число;
КонецФункции
Функция Применить(Массив, ИмяФункции) Экспорт
Результат = Новый Массив;
Для Каждого Элемент Из Массив Цикл
Результат.Добавить(Вычислить("ПреобразованияСервер." + ИмяФункции + "(Элемент)"));
КонецЦикла;
Возврат Результат;
КонецФункции
Вызов ПреобразованияСервер.Применить(Числа, "ВКвадрат") для массива 1, 2, 3 вернёт 1, 4, 9, а с "Удвоить" — 2, 4, 6.
Контекст выполнения: чем заменить замыкания
В JavaScript или Python функция «помнит» переменные, рядом с которыми её создали. В 1С замыканий нет. Обработчик оповещения запускается позже, и локальных переменных вызывающей процедуры к этому моменту уже не существует.
Контекст передают явно:
- 📦 Дополнительные параметры — третий аргумент
ОписаниеОповещения. Обычно этоСтруктурасо всем, что понадобится обработчику: ссылки, номер строки, режим. - 🧾 Реквизиты формы — если обработчик живёт в модуле формы, он видит
Объекти реквизиты формы на момент вызова. - 🔤 Локальный контекст
Выполнить()/Вычислить()— строка выполняется там, где написана, поэтому видит переменные текущей процедуры. Но только в момент вызова, а не позже.
Значения, положенные в ДополнительныеПараметры, фиксируются при создании оповещения. Если между созданием и вызовом пользователь поменял данные формы, читайте их из реквизитов, а не из структуры.
Модуль во втором параметре тоже часть контекста. ЭтотОбъект в модуле формы означает «процедура в этой форме». Для общего клиентского модуля передают сам модуль по имени, например ДиалогиКлиент: Новый ОписаниеОповещения("ПослеВыбора", ДиалогиКлиент). Тогда процедура в нём обязана быть экспортной.
Ошибки при передаче функции и как их исправить
Сообщения ниже — то, что выдаёт платформа в таких случаях. Текст в скобках — имя, которое она не нашла.
Ещё две ошибки проектирования, о которых платформа не предупредит:
- Обработчик объявлен как функция и от него ждут результат.
ВыполнитьОбработкуОповещенияничего не возвращает. Результат обработчик должен записать в реквизит формы или передать дальше следующим оповещением. - Имя метода приходит извне. Если строка для
Выполнить()собирается из данных пользователя, справочника или файла обмена, в неё можно подсунуть любой код. Никогда не выполняйте строку, которую не сформировал ваш собственный код.
Продвинутые техники: цепочки, модуль как значение, БСП
Цепочка оповещений
Асинхронный сценарий из нескольких шагов строят так: каждый обработчик создаёт следующее оповещение и передаёт дальше исходное — то, что нужно вызвать в самом конце.
&НаКлиенте
Процедура Шаг1Завершение(Результат, ДополнительныеПараметры) Экспорт
ДополнительныеПараметры.Вставить("ИмяФайла", Результат);
Следующий = Новый ОписаниеОповещения("Шаг2Завершение", ЭтотОбъект, ДополнительныеПараметры);
ЗапуститьШаг2(Следующий);
КонецПроцедуры
В ДополнительныеПараметры удобно хранить ключ ОписаниеЗавершения с оповещением верхнего уровня и вызвать его на последнем шаге.
Модуль как значение
На сервере общий модуль можно положить в переменную и передать как параметр. В конфигурациях на БСП для этого есть функция ОбщегоНазначения.ОбщийМодуль("ИмяМодуля"). Она проверяет, что такой модуль есть в метаданных, и возвращает его.
Модуль = ОбщегоНазначения.ОбщийМодуль("ПреобразованияСервер");
Результат = Модуль.Удвоить(21); // 42
Пара «модуль + имя метода» — самая близкая замена указателю на функцию. Модуль проверен, имя короткое, и вызов Модуль.Удвоить() по-прежнему обычный, без строк.
Вызов по полному имени через БСП
В той же библиотеке есть ОбщегоНазначения.ВыполнитьМетодКонфигурации(ИмяМетода, Параметры). Ей передают полное имя экспортной процедуры ("ПреобразованияСервер.Пересчитать") и массив параметров. Перед выполнением она проверяет, что имя похоже на имя метода, а не на произвольный код. Для чужих имён это безопаснее голого Выполнить().
Фоновые задания
Здесь передача метода строкой — штатный механизм платформы:
Параметры = Новый Массив;
Параметры.Добавить(Организация);
ФоновыеЗадания.Выполнить("ЗакрытиеМесяцаСервер.Рассчитать", Параметры);
Первый аргумент — имя экспортной процедуры общего серверного модуля. Это та же идея: метод передаётся как значение-строка, а вызывает его платформа.
Скорость: во что обходится Выполнить и Вычислить
Выполнить() и Вычислить() разбирают строку при каждом вызове. Один вызов на нажатие кнопки незаметен. Вызов в цикле по 100 000 строкам табличной части уже ощутимо медленнее прямого обращения к функции.
Как ускорить:
- 🔁 Вынесите выбор метода за цикл. Определите один раз, какую ветку использовать, а в цикле вызывайте функцию напрямую.
- 🧭 Диспетчер вместо строки. Если вариантов 3–5, обычный
Если … ИначеЕслибыстрее и понятнее:
Функция Преобразовать(Число, Вид) Экспорт
Если Вид = "Удвоить" Тогда
Возврат Удвоить(Число);
ИначеЕсли Вид = "ВКвадрат" Тогда
Возврат ВКвадрат(Число);
КонецЕсли;
ВызватьИсключение СтрШаблон("Неизвестный вид преобразования: %1", Вид);
КонецФункции
- 📦 Передавайте массив целиком. Одна функция, которая принимает массив и возвращает массив, лучше 100 000 отдельных вызовов через строку.
- 🌐 Не гоняйте оповещения через сервер. Оповещение создаётся и вызывается на клиенте. Серверную часть делайте одним вызовом
&НаСервере, а не серией.
Другие подходы без передачи функции
Иногда задачу проще решить, не передавая метод вовсе.
| Подход | Когда подходит | Чем платим |
|---|---|---|
Диспетчер Если … ИначеЕсли | вариантов немного и они известны заранее | при новом варианте правим диспетчер |
| Одинаковый экспортный метод у разных обработок | «плагины»: у каждой обработки своя реализация Выполнить() | нужна договорённость об имени и параметрах |
Оповестить() и ОбработкаОповещения | сообщить всем открытым формам, что данные изменились | широковещательно, без адресата |
| Подписки на события | реакция на запись или проведение объектов | срабатывают для всех объектов из списка |
Асинх / Ждать (платформа 8.3.18 и новее) | цепочка диалогов на клиенте | работает не во всех версиях платформы |
Асинхронные функции отдельно упомянуть стоит. С ключевыми словами Асинх и Ждать цепочку диалогов пишут последовательно, без оповещений:
&НаКлиенте
Асинх Процедура УдалитьСтроки(Команда)
Ответ = Ждать ВопросАсинх("Удалить все строки?", РежимДиалогаВопрос.ДаНет);
Если Ответ = КодВозвратаДиалога.Да Тогда
Объект.Товары.Очистить();
КонецЕсли;
КонецПроцедуры
Если обработчик нужен только для того, чтобы дождаться ответа диалога, этот вариант читается проще. Если же обработчик действительно выбирает вызывающая сторона, ОписаниеОповещения остаётся правильным инструментом.
Отладка: как увидеть, какой метод вызвался
- Точка останова в обработчике. Отладчик заходит в процедуру, вызванную через
ВыполнитьОбработкуОповещения,Выполнить()илиВычислить(), как в обычную. Ставьте точку прямо в целевом методе, а не на строке вызова. - Проверка оповещения в табло. У объекта
ОписаниеОповещенияесть свойстваИмяПроцедуры,МодульиДополнительныеПараметры. Добавьте переменную в табло и посмотрите, что реально передано. - Перехват ошибки при вызове по строке. Оберните вызов, чтобы видеть имя метода в тексте ошибки:
Попытка
Значение = Вычислить("ПреобразованияСервер." + ИмяФункции + "(Элемент)");
Исключение
ВызватьИсключение СтрШаблон("Ошибка вызова %1: %2",
ИмяФункции, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
- Синтаксический контроль не спасёт. Конфигуратор не проверяет имя в строке. Опечатка в
"ВКвадрта"всплывёт только во время выполнения. Поэтому имена методов выносите в одну функцию-список или проверяйте по массиву разрешённых значений.
Поиск ссылок на метод в конфигураторе вызовы через строку не находит. Перед удалением или переименованием экспортной функции поищите её имя глобальным поиском по тексту модулей.