При написании универсальных механизмов проверки модифицированности объектов разработчики часто сталкиваются с неожиданным поведением платформы 1С:Enterprise. Когда обходятся реквизиты объекта и происходит сравнение текущих значений со значениями из ссылки базы данных, реквизит с типом ХранилищеЗначения всегда выдает результат Ложь при проверке на равенство (или Истина при проверке на неравенство).
Рассмотрим следующий пример кода, где проверяется факт изменения реквизитов перед записью объекта:
Если Источник[Реквизит.Имя] <> Источник.Ссылка[Реквизит.Имя] Тогда
Возврат Истина; // Для Хранилища Значения условие срабатывает ВСЕГДА
КонецЕсли;
При этом наблюдается странная картина при выполнении проверок в отладчике:
// Сравнение объекта с самим собой возвращает Истина:
Источник["Хранилище"] = Источник["Хранилище"] // Истина
// Сравнение ссылки с самой собой возвращает Истина:
Источник.Ссылка["Хранилище"] = Источник.Ссылка["Хранилище"] // Истина
// Сравнение объекта и ссылки возвращает Ложь:
Источник["Хранилище"] = Источник.Ссылка["Хранилище"] // Ложь
Разберем подробно механику работы платформы 1С и выясним, почему так происходит и как правильно обрабатывать подобные реквизиты.
Проанализируем внутреннюю устройство типа ХранилищеЗначения. В платформе 1С объект типа ХранилищеЗначения представляет собой не само значение, а специальный объект-обертку в оперативной памяти, содержащий упакованные бинарные данные.
Когда вы обращаетесь к реквизиту ссылки Источник.Ссылка[Реквизит.Имя], платформа считывает данные из базы и создает в памяти новый экземпляр объекта ХранилищеЗначения. В результате оператор равенства = сравнивает не содержимое хранилища, а ссылки на объекты в оперативной памяти.
Даже если создать два абсолютно одинаковых хранилища с одинаковыми простыми значениями, их прямое сравнение вернет Ложь:
Картинка = хзКартинка.Получить();
хзКартинка2 = Новый ХранилищеЗначения(Картинка);
Сообщить(хзКартинка = хзКартинка2); // Ложь!
Таким образом, прямое сравнение двух хранилищ значений стандартными операторами = и <> бессмысленно — они никогда не будут равны, если это два разных экземпляра в памяти.
Чтобы корректно сравнить содержимое, разберем базовый вариант: необходимо сначала извлечь упакованные данные с помощью метода Получить().
Значение1 = Источник[Реквизит.Имя].Получить();
Значение2 = Источник.Ссылка[Реквизит.Имя].Получить();
Если Значение1 <> Значение2 Тогда
// Данные действительно изменились
КонецЕсли;
Важное ограничение: данный способ работает только для простых типов данных (числа, строки, даты, булево, ссылки). Если внутри хранилища находится сложный объект (например, ТаблицаЗначений, Структура, Массив или бинарный файл), то операция Значение1 = Значение2 также вернет Ложь, так как сравнение сложных коллекций и объектов в 1С выполняется по ссылке в памяти.
Если в вашей конфигурации выполняется универсальная проверка модифицированности всех реквизитов объекта, наилучшим решением будет проверка типа реквизита в метаданных и исключение хранилищ значений из общего цикла.
Проанализируем пример кода для пропуска таких реквизитов:
Для Каждого Реквизит Из Источник.Метаданные().Реквизиты Цикл
// Проверяем, содержит ли реквизит тип ХранилищеЗначения
Если Реквизит.Тип.СодержитТип(Тип("ХранилищеЗначения")) Тогда
Продолжить; // Пропускаем прямое сравнение
КонецЕсли;
Если Источник[Реквизит.Имя] <> Источник.Ссылка[Реквизит.Имя] Тогда
Возврат Истина;
КонецЕсли;
КонецЦикла;
Если необходимо точно определить, изменилось ли содержимое хранилища, содержащего произвольные данные или файлы, разберем подход с вычислением хеш-суммы или сериализацией.
Для двоичных данных и файлов наиболее оптимально сравнивать контрольную сумму (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 Тогда
// Содержимое хранилища изменилось
КонецЕсли;
= и <> для типа ХранилищеЗначения сравнивают ссылки на объекты в памяти, а не их внутренние данные.Получить() с последующим сравнением простых значений, хешей двоичных данных или сериализованных строк.ХранилищеЗначения из стандартного цикла проверки реквизитов.