Почему сравнение реквизитов с типом Хранилище значения в 1С всегда возвращает Ложь?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При написании универсальных механизмов проверки модифицированности объектов разработчики часто сталкиваются с неожиданным поведением платформы 1С:Enterprise. Когда обходятся реквизиты объекта и происходит сравнение текущих значений со значениями из ссылки базы данных, реквизит с типом ХранилищеЗначения всегда выдает результат Ложь при проверке на равенство (или Истина при проверке на неравенство).

Рассмотрим следующий пример кода, где проверяется факт изменения реквизитов перед записью объекта:


Если Источник[Реквизит.Имя] <> Источник.Ссылка[Реквизит.Имя] Тогда
    Возврат Истина; // Для Хранилища Значения условие срабатывает ВСЕГДА
КонецЕсли;

При этом наблюдается странная картина при выполнении проверок в отладчике:


// Сравнение объекта с самим собой возвращает Истина:
Источник["Хранилище"] = Источник["Хранилище"] // Истина

// Сравнение ссылки с самой собой возвращает Истина:
Источник.Ссылка["Хранилище"] = Источник.Ссылка["Хранилище"] // Истина

// Сравнение объекта и ссылки возвращает Ложь:
Источник["Хранилище"] = Источник.Ссылка["Хранилище"] // Ложь

Разберем подробно механику работы платформы 1С и выясним, почему так происходит и как правильно обрабатывать подобные реквизиты.

Выясняем причину поведения платформы

Проанализируем внутреннюю устройство типа ХранилищеЗначения. В платформе 1С объект типа ХранилищеЗначения представляет собой не само значение, а специальный объект-обертку в оперативной памяти, содержащий упакованные бинарные данные.

Когда вы обращаетесь к реквизиту ссылки Источник.Ссылка[Реквизит.Имя], платформа считывает данные из базы и создает в памяти новый экземпляр объекта ХранилищеЗначения. В результате оператор равенства = сравнивает не содержимое хранилища, а ссылки на объекты в оперативной памяти.

Даже если создать два абсолютно одинаковых хранилища с одинаковыми простыми значениями, их прямое сравнение вернет Ложь:


Картинка = хзКартинка.Получить();
хзКартинка2 = Новый ХранилищеЗначения(Картинка);

Сообщить(хзКартинка = хзКартинка2); // Ложь!

Таким образом, прямое сравнение двух хранилищ значений стандартными операторами = и <> бессмысленно — они никогда не будут равны, если это два разных экземпляра в памяти.

Способ 1. Извлечение значения из хранилища перед сравнением

Чтобы корректно сравнить содержимое, разберем базовый вариант: необходимо сначала извлечь упакованные данные с помощью метода Получить().


Значение1 = Источник[Реквизит.Имя].Получить();
Значение2 = Источник.Ссылка[Реквизит.Имя].Получить();

Если Значение1 <> Значение2 Тогда
    // Данные действительно изменились
КонецЕсли;

Важное ограничение: данный способ работает только для простых типов данных (числа, строки, даты, булево, ссылки). Если внутри хранилища находится сложный объект (например, ТаблицаЗначений, Структура, Массив или бинарный файл), то операция Значение1 = Значение2 также вернет Ложь, так как сравнение сложных коллекций и объектов в 1С выполняется по ссылке в памяти.

Способ 2. Исключение реквизитов типа ХранилищеЗначения из универсального обхода

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

Проанализируем пример кода для пропуска таких реквизитов:


Для Каждого Реквизит Из Источник.Метаданные().Реквизиты Цикл
    
    // Проверяем, содержит ли реквизит тип ХранилищеЗначения
    Если Реквизит.Тип.СодержитТип(Тип("ХранилищеЗначения")) Тогда
        Продолжить; // Пропускаем прямое сравнение
    КонецЕсли;
    
    Если Источник[Реквизит.Имя] <> Источник.Ссылка[Реквизит.Имя] Тогда
        Возврат Истина;
    КонецЕсли;
    
КонецЦикла;

Способ 3. Сравнение контрольных сумм (хешей) или сериализация

Если необходимо точно определить, изменилось ли содержимое хранилища, содержащего произвольные данные или файлы, разберем подход с вычислением хеш-суммы или сериализацией.

Для двоичных данных и файлов наиболее оптимально сравнивать контрольную сумму (MD5 или SHA256):


Данные1 = Источник[Реквизит.Имя].Получить();
Данные2 = Источник.Ссылка[Реквизит.Имя].Получить();

Если ТипЗнч(Данные1) = Тип("ДвоичныеДанные") И ТипЗнч(Данные2) = Тип("ДвоичныеДанные") Тогда
    Хеш1 = ПолучитьХешДвоичныхДанных(Данные1, ХешФункция.MD5);
    Хеш2 = ПолучитьХешДвоичныхДанных(Данные2, ХешФункция.MD5);
    
    Если Хеш1 <> Хеш2 Тогда
        // Данные файлов отличаются
    КонецЕсли;
КонецЕсли;

Для произвольных структур и таблиц можно использовать сериализацию в XML или JSON, после чего сравнивать полученные текстовые строки:


ЗаписьXML1 = Новый ЗаписьXML;
ЗаписьXML1.УстановитьСтроку();
ЗаписатьXML(ЗаписьXML1, Источник[Реквизит.Имя]);
СтрокаXML1 = ЗаписьXML1.Закрыть();

ЗаписьXML2 = Новый ЗаписьXML;
ЗаписьXML2.УстановитьСтроку();
ЗаписатьXML(ЗаписьXML2, Источник.Ссылка[Реквизит.Имя]);
СтрокаXML2 = ЗаписьXML2.Закрыть();

Если СтрокаXML1 <> СтрокаXML2 Тогда
    // Содержимое хранилища изменилось
КонецЕсли;

Подведение итогов

  1. Операторы сравнения = и <> для типа ХранилищеЗначения сравнивают ссылки на объекты в памяти, а не их внутренние данные.
  2. Чтение реквизита через ссылку всегда создает новый экземпляр в памяти, поэтому сравнение объекта с его ссылкой гарантированно дает Ложь.
  3. Для проверки изменений используйте извлечение данных через метод Получить() с последующим сравнением простых значений, хешей двоичных данных или сериализованных строк.
  4. При написании универсальных алгоритмов исключайте типы ХранилищеЗначения из стандартного цикла проверки реквизитов.
← На главную