В процессе разработки на платформе 1С программисты часто сталкиваются с ситуацией, когда использование ключевого слова Знач при передаче параметра в функцию ведет себя не так, как ожидается. Особенно это касается сложных (агрегатных) типов данных, таких как ДокументОбъект, СправочникОбъект, Массив или Структура. Разберем детально, почему при передаче объекта "по значению" изменения внутри функции отражаются на исходном объекте, и как на самом деле устроена работа с памятью в системе.
Для начала проанализируем, что именно хранится в переменной, когда мы присваиваем ей сложный объект. В 1С, как и во многих современных языках программирования, переменные для простых типов (например, Число, Булево, Дата) хранят непосредственно само значение. Однако для агрегатных типов переменная хранит не весь набор данных объекта, а указатель (ссылку на область памяти), где этот объект физически расположен.
Рассмотрим ситуацию на примере кода:
&НаСервереБезКонтекста
Процедура ПроверкаСтруктурыНаСервере()
Док = Документы.Заявка.СоздатьДокумент();
Док1 = ОбъектПоЗначению(Док);
// Сравнение покажет Истину, так как обе переменные
// указывают на один и тот же адрес в памяти
Б = (Док = Док1);
КонецПроцедуры
&НаСервереБезКонтекста
Функция ОбъектПоЗначению(Знач ВходящийДок)
Возврат ВходящийДок;
КонецФункции
Когда мы вызываем функцию и передаем туда Док с ключевым словом Знач, система действительно копирует значение. Но что является значением в данном случае? Значением является адрес в памяти. В итоге внутри функции переменная ВходящийДок содержит тот же самый адрес, что и исходная переменная Док. Любое изменение реквизита через ВходящийДок — это изменение данных по тому же адресу, к которому обращается и основная процедура.
Важно не путать типы данных ДокументСсылка и ДокументОбъект. Проанализируем их поведение:
СправочникОбъект): Это структура в оперативной памяти, предназначенная для редактирования. Именно здесь проявляется эффект "общего адреса". Даже если объект еще не записан в базу данных (как в примере с СоздатьДокумент()), он уже занимает место в RAM, и переменные лишь "ссылаются" на него.Выясним причину, по которой поведение системы меняется при вызове серверного метода с клиента. Это один из самых важных нюансов платформы 1С.
Когда происходит вызов процедуры с директивой &НаСервере с тонкого клиента, данные должны физически переместиться с одного компьютера (клиента) на другой (сервер). В этот момент вступает в силу механизм сериализации.
Рассмотрим, как это работает:
Знач: Объект упаковывается (сериализуется) на клиенте, передается на сервер, там распаковывается. После выполнения кода на сервере объект снова упаковывается и возвращается на клиент, где исходный объект обновляется новыми данными.Знач: Объект копируется на сервер, но после завершения работы серверного метода изменения не возвращаются обратно на клиент. На сервере вы работали с копией, которая была создана в момент передачи данных через сетевой протокол.Таким образом, внутри одного контекста (например, Сервер -> Сервер) Знач для объектов копирует только указатель. А при переходе границы Клиент -> Сервер Знач фактически создает независимую копию на время выполнения вызова.
Если перед нами стоит задача передать объект в функцию так, чтобы гарантированно не изменить исходные данные, обычного Знач недостаточно. Нам нужно "клонировать" объект. В 1С нет универсального метода Клонировать() для всех типов, поэтому рассмотрим пошагово, как это сделать для разных случаев:
Для объектных типов метаданных (Документы, Справочники) существует встроенный метод. Проанализируем пример:
НовыйОбъект = ИсходныйОбъект.Скопировать();
// Теперь НовыйОбъект и ИсходныйОбъект — это разные сущности в памяти.
// Изменение одного не влияет на другой.
Для таких типов, как Структура, Массив или ТаблицаЗначений, можно использовать преобразование в XML или JSON и обратно. Это гарантирует создание абсолютно нового экземпляра данных.
// Пример через ЗначениеВСтрокуВнутр (для простых структур)
СтрокаДанных = ЗначениеВСтрокуВнутр(ИсходнаяСтруктура);
КопияСтруктуры = ЗначениеИзСтрокиВнутр(СтрокаДанных);
Вернемся к примеру автора: Б = (Док = Док1). Посмотрим на ситуацию внимательнее. В 1С оператор сравнения для объектных типов проверяет идентичность. Это означает, что система спрашивает: "Указывают ли эти две переменные на один и тот же адрес в памяти?".
Если вы создадите два разных документа программно с абсолютно идентичными реквизитами:
Док1 = Документы.Заявка.СоздатьДокумент();
Док2 = Документы.Заявка.СоздатьДокумент();
// Заполним их одинаково...
Б = (Док1 = Док2); // Вернет ЛОЖЬ
Несмотря на то, что данные внутри одинаковы, это два разных объекта в памяти. В примере же из обсуждения, Док1 получил значение указателя от Док через функцию, поэтому результат сравнения — Истина.
Подводя итог, при работе с агрегатными типами в 1С следует помнить:
Знач для объектов не создает копию данных внутри одного контекста выполнения. Оно лишь защищает саму переменную от перезакрепления за другим объектом, но не защищает свойства объекта от изменения.Скопировать() или механизмы сериализации.=.Понимание этих механизмов управления памятью позволяет избежать трудноуловимых ошибок, когда данные "внезапно" меняются в родительских процедурах после вызова вспомогательных функций. Чтобы вовремя обнаружить подобные расхождения в учете, удобно автоматически сравнить бухгалтерские и налоговые проводки между базами 1С.