Ссылка или Объект

Вопрос №1 на собеседованиях по 1С: «Чем ссылка на элемент отличается от объекта?» Кто путается здесь — обычно проваливает и остальное.

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

Что стоит за вопросом

В 1С у каждого справочника и документа есть два разных типа. Например, для справочника «Контрагенты»: СправочникСсылка.Контрагенты и СправочникОбъект.Контрагенты. Их путают почти все новички, а на них держится половина кода конфигурации, поэтому вопрос и стал классикой.

  • Ссылка — это, по сути, идентификатор записи (внутри — уникальный GUID). Она лёгкая: не грузит все поля, живёт в реквизитах, параметрах запросов, полях документов. Через ссылку можно читать реквизиты (Контрагент.ИНН), но нельзя менять и записывать.
  • Объект — это уже «распакованная» запись: все реквизиты, табличные части, возможность изменить и вызвать Записать(). Он тяжелее: чтобы его получить, платформа читает из базы полную запись.

Метафора: ссылка — это номер телефона человека в записной книжке (компактно, легко хранить и сравнивать), а объект — сам человек, пришедший в офис (можно поговорить, что-то изменить, но «вызвать» его — целое дело).

Как получить объект из ссылки

Переход от ссылки к объекту — метод ПолучитьОбъект(). Обратно объект знает свою ссылку через свойство Ссылка.

// Есть ссылка (лёгкая) — читаем реквизит без загрузки всего объекта
СсылкаКонтрагента = Справочники.Контрагенты.НайтиПоКоду("00-0001");
Сообщить("ИНН: " + СсылкаКонтрагента.ИНН); // чтение через ссылку — можно

// Нужно ИЗМЕНИТЬ — получаем объект (тяжёлая операция: чтение из базы)
ОбъектКонтрагента = СсылкаКонтрагента.ПолучитьОбъект();
ОбъектКонтрагента.Комментарий = "Проверен менеджером";
ОбъектКонтрагента.Записать();

Попытка изменить реквизит прямо у ссылки — ошибка: ссылка предназначена только для чтения.

СсылкаКонтрагента.Комментарий = "Тест"; // ОШИБКА: у ссылки нет изменяемых реквизитов для записи

Результат:

{Обработка.Пример.МодульОбъекта(12)}: Ошибка при вызове метода контекста:
значение не является значением объектного типа (запись невозможна).

Почему сравнивать надо ссылки, а менять — объект

Это главная мысль урока. Чтобы понять, один и тот же ли это контрагент, сравнивают ссылки: ссылка — это тождество записи, сравнение лёгкое и корректное.

Если Документ.Контрагент = ВыбранныйКонтрагент Тогда
    // сравнение ссылок: один и тот же элемент справочника
    Сообщить("Это тот же контрагент");
КонецЕсли;

// А если сравнивать объекты — так делать НЕ надо:
Если Документ.Контрагент.ПолучитьОбъект() = ВыбранныйКонтрагент.ПолучитьОбъект() Тогда
    // два РАЗНЫХ объекта в памяти, даже если это одна запись — сравнение бессмысленно
КонецЕсли;

Логика простая: два вызова ПолучитьОбъект() для одной записи создают две разные копии в памяти — сравнивать их как объекты нельзя. А вот ссылки на одну запись всегда равны. Менять же данные можно только у объекта: у ссылки нет Записать().

Как это работает: цена ПолучитьОбъект()

Вызов ПолучитьОбъект() — это реальное чтение полной записи из базы: все реквизиты и все табличные части поднимаются в память. Одна операция дешёвая, но в цикле по тысячам элементов это тысячи обращений к СУБД.

Поэтому правило: не получайте объект без надобности. Нужно только прочитать пару реквизитов — читайте через ссылку (или, ещё лучше, запросом сразу по многим элементам). Получайте объект лишь тогда, когда собираетесь изменить и записать данные.

// ПЛОХО: объект ради одного реквизита, да ещё в цикле
Для Каждого Стр Из ТаблицаКонтрагентов Цикл
    Объект = Стр.Контрагент.ПолучитьОбъект(); // чтение всей записи из базы
    Сообщить(Объект.ИНН);                       // а нужен был только ИНН
КонецЦикла;

// ХОРОШО: реквизит читается через ссылку, без загрузки объекта
Для Каждого Стр Из ТаблицаКонтрагентов Цикл
    Сообщить(Стр.Контрагент.ИНН);
КонецЦикла;

Как отвечать

Идеальный ответ в трёх фразах: «Ссылка — лёгкий указатель на запись, объект — её полная изменяемая копия в памяти. Читать реквизиты и сравнивать на тождество можно и нужно по ссылке, потому что ссылки на одну запись всегда равны. Чтобы что-то изменить, я беру объект через ПолучитьОбъект() и вызываю Записать(), но помню, что это чтение всей записи из базы, поэтому в циклах его избегаю».

Хороший бонус — упомянуть, что запрос почти всегда возвращает ссылки, а не объекты, и это одна из причин, почему запросы быстрые.

Частые ошибки на собеседовании

  • Считать, что ссылка и объект — «одно и то же, просто разные слова». Это разные типы с разной ценой и назначением.
  • Пытаться менять данные через ссылку — у неё нет Записать().
  • Сравнивать элементы через ПолучитьОбъект(): получаются две разные копии, сравнение всегда «не равно».
  • Дёргать ПолучитьОбъект() в цикле ради одного реквизита — тысячи лишних чтений из базы.
  • Думать, что запрос возвращает объекты. Он возвращает ссылки и значения полей.

Итоги

  • Ссылка — лёгкий указатель (тождество записи); объект — тяжёлая изменяемая копия с Записать().
  • Сравнивают ссылки, изменяют — объект.
  • ПолучитьОбъект() читает полную запись из базы — дорого, особенно в циклах.
  • Читать реквизиты можно через ссылку; получать объект — только когда реально меняем данные.
Проверьте себя
1. Нужно узнать ИНН контрагента, ничего не меняя. Как правильнее?
AПолучить объект через ПолучитьОбъект() и прочитать ИНН у объекта
BПрочитать реквизит прямо у ссылки: СсылкаКонтрагента.ИНН
CНикак — ИНН доступен только после Записать()
DСравнить ссылку с эталонным контрагентом
2. Почему нельзя проверять «это один и тот же элемент?» через сравнение результатов ПолучитьОбъект()?
AПотому что ПолучитьОбъект() всегда возвращает пустое значение
BПотому что объекты нельзя сравнивать оператором =
CПотому что два вызова создают две разные копии записи в памяти, и они не равны
DПотому что сравнение объектов работает медленнее сравнения ссылок