В повседневной разработке на платформе 1С:Предприятие мы часто используем оператор сравнения =. Казалось бы, всё просто: примитивные типы (числа, строки) сравниваются по значению, а объекты (ссылки, структуры, таблицы) — по ссылке. Однако, если мы начнем сравнивать объекты, созданные через конструктор Новый, результаты могут поставить в тупик даже опытного разработчика. Рассмотрим, почему в одних случаях два новых объекта равны, а в других — нет, и как это знание применить на практике.
Для того чтобы понять логику платформы, мы должны заглянуть «под капот». В языке 1С для каждого типа данных на уровне ядра (написанного на C++) реализован свой алгоритм сравнения. Существует две основные стратегии:
Структура, даже с идентичными ключами и значениями, они не будут равны, так как это разные экземпляры.Истина. Это часто применяется для «объектов-описателей».Проанализируем конкретные примеры из «гуру-теста» и выясним причины такого поведения.
Рассмотрим ситуацию, когда конструктор Новый возвращает значения, которые при сравнении дают Истина. Проанализируем следующий код:
ОписаниеТипов1 = Новый ОписаниеТипов("Число, Строка") = Новый ОписаниеТипов("Число, Строка"); // Истина
ОписаниеТипов2 = Новый ОписаниеТипов("Строка, Число") = Новый ОписаниеТипов("Число, Строка"); // Истина
КвалификаторыЧисла = Новый КвалификаторыЧисла(10, 2) = Новый КвалификаторыЧисла(10, 2); // Истина
Почему это происходит? Рассмотрим объект ОписаниеТипов. При его создании платформа выполняет внутреннюю нормализацию: типы сортируются, дубликаты удаляются. Более того, 1С активно использует кэширование описаний типов для оптимизации работы с базой данных. В результате, даже если вы поменяли местами «Строка» и «Число» в строке конструктора, внутреннее состояние объектов будет идентичным, и платформа вернет Истина.
Аналогично ведут себя КвалификаторыЧисла, КвалификаторыСтроки и КвалификаторыДаты. Это неизменяемые (immutable) объекты, назначение которых — просто описывать параметры данных. Хранить в памяти тысячи одинаковых описателей бессмысленно, поэтому система сравнивает их по содержимому.
Многих удивляет поведение объектов для работы с XML и JSON. Проверим:
ПараметрыЗаписиXML = Новый ПараметрыЗаписиXML = Новый ПараметрыЗаписиXML; // Истина
Несмотря на то что это объекты с множеством свойств (кодировка, отступы и т.д.), платформа считает их равными, если настройки совпадают. Это происходит потому, что такие объекты часто используются в механизмах сериализации. Для системы важно быстро определить, отличаются ли настройки записи, чтобы понимать, нужно ли перестраивать поток вывода.
Теперь разберем случаи, когда Новый всегда порождает уникальный, не равный другому объект:
Структура = Новый Структура("Ключ") = Новый Структура("Ключ"); // Ложь
СжатиеДанных = Новый СжатиеДанных(9) = Новый СжатиеДанных(9); // Ложь
ОписаниеОповещения = Новый ОписаниеОповещения("Имя", ЭтотОбъект) = Новый ОписаниеОповещения("Имя", ЭтотОбъект); // Ложь
Структура и Массив: Это мутабельные коллекции. Вы можете изменить их содержимое в любой момент. Если бы платформа сравнивала их по значению, ей пришлось бы при каждом сравнении обходить все элементы коллекции, что крайне непроизводительно. Поэтому для них всегда используется сравнение указателей в памяти.
СжатиеДанных: Это интересный случай. Несмотря на то что у объекта всего один параметр (уровень сжатия), разработчики платформы не реализовали для него сравнение по значению. Вероятно, это связано с тем, что объект является оберткой над низкоуровневыми библиотеками сжатия, и каждый экземпляр управляет своим состоянием потока.
Работа с метаданными в 1С подчиняется правилу «единственного экземпляра» (Singleton). Выясним, почему это так, на примере:
Ф1 = Метаданные = Метаданные; // Истина
Ф3 = Метаданные.Справочники.Валюты = Метаданные.Справочники.Валюты; // Истина
Ф4 = Метаданные.Справочники.Валюты = Метаданные.Справочники.Валюты.Реквизиты.Наименование.Родитель(); // Истина
Платформа загружает дерево метаданных конфигурации в память один раз при запуске сеанса. Когда вы обращаетесь к Метаданные.Справочники.Валюты, вы получаете указатель на конкретный узел этого дерева. Любой другой путь, приводящий к этому же узлу (например, через метод Родитель() подчиненного реквизита), вернет тот же самый указатель. Поэтому сравнение объектов метаданных всегда надежно и быстро.
Одной из самых неожиданных находок является сравнение областей табличного документа. Проанализируем ситуацию:
Результат = ТабДок.Область(1, 1) = ТабДок.Область(1, 1); // Ложь!
Даже если координаты области (Y1, X1, Y2, X2) абсолютно одинаковы, результат будет Ложь. Это происходит потому, что метод Область() каждый раз создает новый объект-прокси для манипуляции ячейками. Платформа не выполняет «канонизацию» этих объектов. Если вам нужно проверить, являются ли две переменные одной и той же областью, сравнивайте их свойства Имя или координаты вручную.
На основе тестов и анализа поведения платформы, мы можем составить список типов, для которых сравнение Новый Тип(...) = Новый Тип(...) с большой вероятностью вернет Истина:
ОписаниеТиповКвалификаторыЧисла, КвалификаторыСтроки, КвалификаторыДаты, КвалификаторыДвоичныхДанныхУникальныйИдентификаторЦвет, Шрифт (если параметры совпадают)СтандартныйПериодПолеКомпоновкиДанных, ПараметрКомпоновкиДанныхГраница, МоментВремениПараметрыЗаписиXML, ПараметрыЧтенияXMLФорматированнаяСтрокаСамый важный аспект применения этих знаний — использование объектов в качестве ключей в коллекции Соответствие.
Вспомним: поиск в Соответствие работает по тем же правилам, что и оператор =. Если вы используете в качестве ключа ОписаниеТипов, вы сможете получить значение из соответствия, передав туда «новый» объект с тем же составом типов. Но если вы используете в качестве ключа Структуру или СжатиеДанных, вы никогда не найдете значение по новому экземпляру, так как они не будут равны (ссылки разные).
Подведем итог: Не полагайтесь на интуицию при сравнении объектов. Если объект сложный (коллекция или прокси-объект вроде области таблицы), сравнивайте его по ключевым идентификаторам или свойствам. Если объект описывает тип или формат — смело используйте прямое сравнение.