Перечитывая документацию на ИТС по теме использования "Знач" задумался о недостающих наглядных примерах написанного в ней, чтобы детальнее разобрать/понять суть и, неожиданно для себя, наткнулся на некоторые особенности, о которых и хочу наглядно рассказать в публикации и продемонстрировать их.
Выражаю большую благодарность моим друзьям и коллегам, негласным соавторам данной публикации, за помощь в понимании сути, обсуждениях, разбор и детализацию отдельных, наиболее сложных и неявных особенностей поведения передачи параметров, в частности "Знач", а именно Сергею Шикуткину и Руслану Григорьеву.
Переписывать весь блок документации, касающийся данной публикации, не вижу смысла, а лишь хочу остановиться и сделать выжимку в некоторых местах, на которых вначале у меня возникли затруднения в понимании смысла написанного, и привести наглядные примеры с пояснением происходящего, после самостоятельного осмысления и практического разбора некоторых из пунктов документации.
Итак:
Пример1
4.7.4.2. Вызов без передачи управления с клиента на сервер
Если вызов происходит без передачи управления между клиентом и сервером (вызов происходит только на клиентской или только на серверной стороне), то по умолчанию параметры передаются по ссылке. При этом изменение формального параметра ведет к изменению соответствующего фактического параметра. С помощью модификатора Знач перед именем формального параметра можно указать, что параметр передается по значению. В этом случае нельзя изменить значение фактического параметра путем присвоения формальному параметру какого-либо значения.
&НаКлиенте
Процедура МояПроцедура()
А = 100;
ПоСсылке(А);
// Переменная А = 40, так как в теле процедуры значение
// параметра Параметр1 изменено на 40.
// Изменение переменной А произошло потому, что параметр передавался по ссылке
А = 100;
ПоЗначению(А);
// Переменная А = 100, несмотря на то, что в в теле процедуры
// значение параметра Параметр1 изменено на 40.
// Изменение переменной А не произошло, так как параметр передавался по значению
КонецПроцедуры
&НаКлиенте
Процедура ПоСсылке(Параметр1)
Параметр1 = 40;
КонецПроцедуры
&НаКлиенте
Процедура ПоЗначению(Знач Параметр1)
Параметр1 = 40;
КонецПроцедуры
Тут все понятно. Вопросов нет.
Далее следует такой текст, который меня заинтересовал больше всего и вроде бы из кода тоже все понятно и ясно:
Однако необходимо помнить следующую особенность: если параметром передается агрегатный объект, то невозможно присвоить фактическому параметру другое значение, но возможно изменить сам переданный объект. Например, если в процедуру по значению передан массив, то можно очистить этот массив методом Очистить(), но нельзя изменить в вызывающей процедуре само значение параметра
"Комментами по коду отметил..." "Отсюда делаем логический вывод"
&НаКлиенте
Процедура МояПроцедура()
Массив = Новый Массив;
Массив.Добавить(12);
Массив.Добавить(18);
// В массиве есть два элемента
ПоЗначению(Массив);
// Массив пустой, но это по прежнему массив, а не Число
КонецПроцедуры
// Параметр передается по значению
&НаКлиенте
Процедура ПоЗначению(Знач Параметр)
// В массиве два значения
Параметр.Очистить();
// В массиве нет значений!
// Меняем формальный параметр
Параметр = 14;
// Изменено значение только формального параметра
КонецПроцедуры
Попробуем промоделировать данную особенность, которая ни так уж и выделяется в тексте. Мы хотим изменить значение параметра в другой процедуре и не возвращать новое полученное значение в фактический параметр "Структура", поэтому с радостью используем "Знач" перед формальным параметром в процедуре "ИзменитьСтруктуру1". Помним, что у нас в данном случае "Вызов без передачи управления с клиента на сервер", т.е. либо весь код выполняется только на клиенте, либо только на сервере, а значит параметр передается по ссылке, или по значению, если указано Знач. (Это не буду разжевывать. Есть в документации выше. Читаем ее. Далее по тексту буду просто вставлять цитаты из документации):
Передача параметров процедур и функций выполняется двумя способами. Один способ называется передачей по ссылке и представляет собой передачу не конкретного значения параметра, а адреса памяти (ссылки на переменную), где расположено это значение. Изменение переданного значения в вызываемой процедуре или функции приведет к изменению передаваемой переменной в вызывающем методе.
&НаСервере
Процедура Тест1()
Структура = Новый Структура;
Структура.Вставить("Тест", "Привет"); // Структура.Тест = "Привет"
// После вызова и выполнения процедуры
// Структура.Тест = "Пока"
ИзменитьСтруктуру1(Структура);
Результат = Структура.Тест; // Структура.Тест = "Пока"
Сообщить(Результат);
КонецПроцедуры
&НаСервере
Процедура ИзменитьСтруктуру1(Знач Структура)
Структура.Тест = "Пока";
КонецПроцедуры
Комментами по коду отметил, что после выполнения процедуры "ИзменитьСтруктуру1", вернувшись обратно в процедуру "Тест1", неожиданно обнаруживаем, что значение "Структура.Тест" поменялось с "Привет" на "Пока". В чем же дело? Мы же установили "Знач" во второй процедуре и значение не должно было вернуться обратно! Перечитываем этот кусок документации еще раз: "Однако необходимо помнить", пробуем моделировать еще раз, но в этот раз попытаемся изменить тип формального параметра "Структура", присвоив новое значение переменной, тип число, значение = 300:
&НаСервере
Процедура Тест1()
Структура = Новый Структура;
Структура.Вставить("Тест", "Привет"); // Структура.Тест = "Привет"
// После вызова и выполнения процедуры
// Все еще Структура.Тест = "Пока", хотя мы изменили переменную на число 300
ИзменитьСтруктуру1(Структура);
Результат = Структура.Тест; // Структура.Тест = "Пока"
Сообщить(Результат);
КонецПроцедуры
&НаСервере
Процедура ИзменитьСтруктуру1(Знач Структура)
Структура.Тест = "Пока";
Структура = 300; // Абстрактное число
КонецПроцедуры
И что же мы видим? Переменная Структура не поменяла тип. Как была тип = Структура, так и осталась. Знач отработало корректно. Но при этом внутри самой структуры значение элемента все-таки поменялось на "Пока". Так отработало Знач или нет?!!
Отсюда делаем логический вывод: Когда используем в передаваемом параметре не примитивные объекты (Строка, Число и т.п.), но агрегированные объекты (Структура, Массив и т.п.) и после передачи формального параметра с использованием "Знач" уже не получится изменить сам фактический параметр в точке вызова, но используя свойства/методы агрегированного объекта, в данном случае Структура, можно менять значения элементов, которые радостно вернутся через "Знач" обратно в фактический параметр в точке вызова! В чем мы наглядно и убедились. Теперь становится более понятно, что имеется ввиду в тексте документации: "Однако необходимо помнить"
Пример2
Попробуем сразу перейти к смене режима управления:
4.7.4.3. Вызов с передачей управления с клиента на сервер и смоделировать тот же самый пример, чтобы посмотреть, что будет в данном случае.
&НаКлиенте // Внимательно в этой строке
Процедура Тест2(Команда)
Структура = Новый Структура;
Структура.Вставить("Тест", "Привет"); // Структура.Тест = "Привет"
// После вызова и выполнения процедуры
// Неожиданно остается Структура.Тест = "Привет". Почему? Давайте разбираться далее в тексте публикации.
ИзменитьСтруктуру1(Структура);
Результат = Структура.Тест;
Сообщить(Результат);
КонецПроцедуры
&НаСервере // Внимательно в этой строке. Изменилось управление с клиента на сервер
Процедура ИзменитьСтруктуру1(Знач Структура)
Структура.Тест = "Пока";
КонецПроцедуры
Почему же теперь после выполнения второй вызванной второй процедуры значение элемента структуры не изменилось?
Прочтем документацию:
Вызов процедур и функций с передачей управления между клиентом и сервером характерен тем, что в общем случае при таком вызове изменяется компьютер, на котором происходит работа вызываемого метода. Это происходит потому, что (в общем случае) клиент работает на одном компьютере, а сервер - на другом. Следовательно, нельзя говорить о передаче параметров по ссылке, т. к. один компьютер не может получить прямого доступа к памяти другого компьютера. При работе в файл-серверном варианте клиент и сервер представляют собой один компьютер, но на логику взаимодействия это не влияет. Поэтому передача параметров в случае клиент-серверного взаимодействия происходит особым образом:
- При передаче управления с клиента на сервер (и обратно) всегда передаются копии параметров. При вызове серверной процедуры или функции с клиента происходит создание копии фактического параметра и передача этой копии на сторону сервера. При возврате управления с сервера на клиента, также происходит создание копии формального параметра (с которым происходила работы в вызванной процедуре или функции) для передачи обратно на клиента.
- Если формальный параметр указан с модификатором Знач, то значение параметра будет передаваться только при вызове процедуры или функции и не будет передаваться обратно при возврате управления на клиента.
Т.е. при смене управления с клиента на сервера или с сервера на клиент
всегда передаются копии параметров
Логический вывод на основе примера: значит ни о какой передаче по ссылке речи быть уже не может и указание "Знач" блокирует уже возврат, как самой новой созданной копии во второй процедуре, так и его элементов, даже если мы изначально передавали в точке вызова агрегируемый объект (Структура, Массив и т.п.), именно поэтому в точке вызова мы видим неизмененный фактический параметр, сам объект и значения его элементов.
Логический вывод на основе двух вышеизложенных примеров: Важно всегда обращать внимание меняется ли управление с клиента на сервер или с сервера на клиент.
Пример3
4.7.4.2. Вызов без передачи управления с клиента на сервер
Попробуем сделать похожий пример, но чуть изменим код во второй вызываемой процедуре. Прошу обратить внимание - в данном примере смены управления не происходит.
&НаСервере // Внимательно в этой строке
Процедура Тест3()
Структура = Новый Структура;
Структура.Вставить("Тест", "Привет"); // Структура.Тест = "Привет"
// После вызова и выполнения процедуры
// Ожидаем увидеть тут Структура.Тест = "Пока"
// Но неожиданно прилетает обратно Структура.Тест = "ПриветПока".
// Почему? Давайте разбираться далее в тексте публикации.
ИзменитьСтруктуру2(Структура);
Результат = Структура.Тест;
Сообщить(Результат);
КонецПроцедуры
&НаСервере // Внимательно в этой строке. Смены управления нет.
Процедура ИзменитьСтруктуру2(Знач Структура)
Структура.Тест = "ПриветПока"; // 2. А на самом деле вернется это значение
Структура = Новый Структура;
Структура.Вставить("Тест", "Пока"); // 1. Якобы вернется это значение обратно
КонецПроцедуры
А что вообще происходит?!! Мы же изменили значение элемента во второй вызываемой процедуре с "ПриветПока" на "Пока" и логично было бы увидеть это значение в точке вызова в первой процедуру, но тем не менее мы видим значение "ПриветПока". Единственное разумное объяснение, которое я здесь вижу это - в момент передачи в параметр передан по значению указатель на структуру (или это ссылка на нее же?)
Т.к. это ссылочный тип, то несмотря на передачу его по значению, модификация структуры внутри вызванной второй процедуры видна и снаружи. А вот присвоение другого значения (благодаря передаче по значению) не влияет на переменную снаружи вызова - это мы обсуждали выше в Примере1, т.е. мы уже не сможем Структуру заменить на Массив например, или на Число и т.п.
Но простите, ведь мы же по отладке видим во второй вызванной процедуре, что Структура.Тест получила новое значение = "Пока". А вот тут можно объяснить так - без смены контекста (во второй процедуре) мы работаем с тем же самым объектом. Но при возвращении в исходную первую процедуру он берёт изначальный указатель, который был до вызова второй процедуры. Как тебе такое, Илон Маск?))
UPD_20250111: Пояснение в комментах, от коллеги по цеху, Андрея Капустина:
Ведь вы создали новую область памяти с новой ссылкой. Просто ей присвоилось имя от другой области. А параметр остался по своему адресу, но обратится к нему вы уже не можете, т.к. затерли имя. Потенциальная утечка памяти.
Пример4
4.7.4.3. Вызов с передачей управления с клиента на сервер
Обратимся дальше к документации и прочитаем еще один нюанс:
- Если для нескольких формальных параметров указывается одно и то же фактическое значение, то создается столько копий фактического значения, для скольких формальных параметров используется значение.
- Если в качестве формальных параметров указано значение одной и той же переменной, то после возврата управления с сервера значение этой переменной будет установлено в значение самого правого формального параметра (без модификатора Знач), который изменялся в вызываемой функции.
&НаКлиенте // Внимательно в этой строке
Процедура Тест4(Команда)
Структура = Новый Структура;
Структура.Вставить("Тест", "Привет"); // Структура.Тест = "Привет"
// После вызова и выполнения процедуры
// Какое значение вернется теперь в Структура.Тест?
// Вернется Структура.Тест = "Пока2"
// Почему? Давайте разбираться далее в тексте публикации.
ИзменитьСтруктуру3(Структура, Структура, Структура);
Результат = Структура.Тест;
Сообщить(Результат);
КонецПроцедуры
&НаСервере // Внимательно в этой строке. Изменилось управление с клиента на сервер
Процедура ИзменитьСтруктуру3(Структура, Структура2, Знач Структура3)
Структура.Вставить("Тест", "Пока");
Структура2.Вставить("Тест", "Пока2");
Структура3.Вставить("Тест", "Пока3");
КонецПроцедуры
Сначала даже не смог понять, что написано вообще здесь:
то после возврата управления с сервера значение этой переменной будет установлено в значение самого правого формального параметра (без модификатора Знач), который изменялся в вызываемой функции
Что?!! Смотрим в код внимательно и понимаем, что в момент вызова, так как происходит смена управления, создается отдельная копия формального параметра из одного фактического параметра, ну тут вроде уже все понятно. Пришли во вторую процедуру, выполнили изменения каждого параметра, хотим вернуться назад в первую процедуру. А какое значение теперь укладывать в Структура.Тест? Вот тут и становится более понятной документация = возьмется самое крайнее правое значение формального параметра без Знач, т.е. значение из параметра Структура2.Тест = "Пока2".
UPD_20250111: Пример5
4.7.4.3. Вызов с передачей управления с клиента на сервер
Попробуем смоделировать тот же пример, добавив еще 1 формальный параметр и поменяв их местами, вклинив параметр со Знач по середине:
&НаКлиенте // Внимательно в этой строке.
Процедура Тест5(Команда)
Структура = Новый Структура;
Структура.Вставить("Тест", "Привет"); // Структура.Тест = "Привет"
// После вызова и выполнения процедуры
// Какое значение вернется теперь в Структура.Тест?
// Вернется Структура.Тест = "Пока4"
// Почему? Давайте разбираться далее в тексте публикации.
ИзменитьСтруктуру4(Структура, Структура, Структура, Структура);
Результат = Структура.Тест;
Сообщить(Результат);
КонецПроцедуры
&НаСервере // Внимательно в этой строке. Изменилось управление с клиента на сервер
Процедура ИзменитьСтруктуру4(Структура, Структура2, Знач Структура3, Структура4)
Структура.Вставить("Тест", "Пока");
Структура2.Вставить("Тест", "Пока2");
Структура3.Вставить("Тест", "Пока3");
Структура4.Вставить("Тест", "Пока4");
КонецПроцедуры
Как мы видим наглядно, документация отработала, как заявлено. Возвращено обратно крайнее правое значение без Знач.
Есть еще один кусок документации, который я сам не могу до конца понять:
4.7.4.3. Вызов с передачей управления с клиента на сервер
Если во время исполнения серверного метода произошло исключение, то значения параметров метода, переданных по ссылке, будут неопределены при возврате на сторону клиента.
Тут опять же рассуждаю чисто логически, опираясь на саму же документацию:
Этого пункта в принципе быть не может априори, потому что нам сказано, что при смене управления с клиента на сервер или с сервера на клиент ВСЕГДА передаются копии параметров! О какой тогда ссылочности здесь вообще может вестись речь? Ссылка тут бессмыслена априори. Потому что это ВСЕГДА разные машины при смене управления, значит не можем задействовать одну и ту же область памяти компа (даже при файловом варианте базы нам сказано, что принцип клиент-серверности не изменяется в платформе). Значит логично предположить, что эта часть документации неверная либо она досталась в наследство от предыдущих вариантов платформы? А что ВЫ думаете по поводу этого пункта. Велкам ту комментс.
Мой коллега Сергей Шикуткин ответил так:
Логика такая: нужно вернуться во времена когда не было клиент-сервера, а всë выполнялось в одном контексте. Но раз это есть, то, возможно, когда только внедрили клиент-сервер, возникла такая особенность и поэтому еë добавили в документацию. А потом поведение пофиксили.
Фух! Ну и как вам?
Данная публикация создана для того, чтобы наглядно можно было более детальнее разжевать куски документации, потому что иногда ее нужно по-моему читать под кокаином)).
Все описанное мной выше, можно легко скачать в приложенной обработке и самостоятельно пройти по отладке и убедиться в правдивости изложенных примеров. Обработка максимально простая. Ее цель - дать возможность отладки. Ничего более.
Я постарался максимально просто уложить все мысли в текст, допускаю, что в некоторых местах не подобрал более точных слов и определений, но я старался насколько мог, ибо объяснить текстом все вышеизложенное довольно сложно, нужно пробовать ручками, именно поэтому прикладываю обработку, чтобы вы могли самостоятельно убедиться во всем вышесказанном мною, и даже возможно найти еще варианты нюансов/особенностей неявного поведения, которые так слабо освещены примерами в документации, чем я буду очень благодарен в виде ваших комментов ниже.
Пожелания, замечания, предложения - оставляйте в комментах.
Найдете ошибку? - Пишите в комментах. Исправлю.
Я всегда готов прислушаться к вашей критике.
Лучшая ваша благодарность - плюс на статью.
Обработку тестировал на 1С:Предприятие 8.3 (8.3.25.1374)
UPD_20250110: Думаю, надо добавить ссылку на эту статью. Она очень полезна. По ссылке или по значению? Ключевое слово Знач и с чем его едят