В повседневной разработке на платформе 1С:Предприятие 8 мы часто используем коллекцию Соответствие (аналог Map или Dictionary в других языках). Она удобна тем, что позволяет хранить пары ключ-значение, где ключом может выступать практически любой тип данных. Однако программисты часто сталкиваются с досадным ограничением: у Соответствия отсутствует метод, аналогичный методу Свойство() у Структуры. В этой статье мы подробно разберем, почему простая проверка на Неопределено не всегда работает, и рассмотрим эффективные способы решения этой задачи. Для решения проблемы отсутствия единого интерфейса у стандартных коллекций можно использовать класс-надстройку для универсальных коллекций.
Стандартный способ получения значения из соответствия выглядит так: Значение = МоеСоответствие[Ключ] или Значение = МоеСоответствие.Получить(Ключ). Если ключа в коллекции нет, 1С возвращает Неопределено. Проблема возникает в ситуации, когда сам ключ в коллекции существует, но в качестве его значения специально записано Неопределено. В этом случае мы не можем по результату метода Получить() понять: ключа нет вовсе или он есть, но пустой.
Разберем ситуацию на примере. Представим, что мы десериализуем JSON, где поле может содержать null. Платформа преобразует null в Неопределено. Если нам нужно четко знать, присутствовало ли это поле в исходном файле, обычное обращение к коллекции нам не поможет.
Проанализируем наиболее изящный с точки зрения производительности "хак". Суть метода заключается в том, что мы пытаемся удалить элемент по ключу и смотрим, изменилось ли количество элементов в коллекции. Поскольку Соответствие внутри реализовано как хеш-таблица, операция удаления выполняется очень быстро (сложность O(1)), независимо от общего размера коллекции.
Рассмотрим готовую функцию, реализующую этот алгоритм:
Функция СоответствиеСодержитКлюч(Соответствие, Ключ)
// Если значение не Неопределено, значит ключ точно есть
Если НЕ Соответствие[Ключ] = Неопределено Тогда
Возврат Истина;
КонецЕсли;
// Если значение Неопределено, проверяем наличие ключа физически
КоличествоДо = Соответствие.Количество();
// Пытаемся сохранить значение перед удалением
// (в данном контексте оно и так Неопределено, но для универсальности можно запомнить)
Соответствие.Удалить(Ключ);
Если КоличествоДо = Соответствие.Количество() Тогда
// Количество не изменилось - значит ключа не было
Возврат Ложь;
Иначе
// Количество уменьшилось - ключ был. Восстанавливаем его.
Соответствие.Вставить(Ключ, Неопределено);
Возврат Истина;
КонецЕсли;
КонецФункции
Важный нюанс: этот метод изменяет коллекцию (удаляет и вставляет обратно), что может быть критично в многопоточных средах или при обходе коллекции в цикле. Однако для большинства задач это самый быстрый способ.
Интересная особенность платформы заключается в том, что объект ФиксированноеСоответствие ведет себя иначе при обращении через квадратные скобки []. В отличие от обычного соответствия, обращение к несуществующему ключу в нем вызывает исключение. При этом метод Получить() по-прежнему возвращает Неопределено.
Разберем, как это можно использовать для проверки:
Функция ЕстьКлючВФиксСоответствии(ОбычноеСоответствие, Ключ)
// Преобразуем в фиксированное (создается копия!)
ФиксСоответствие = Новый ФиксированноеСоответствие(ОбычноеСоответствие);
Попытка
Тест = ФиксСоответствие[Ключ];
Возврат Истина;
Исключение
Возврат Ложь;
КонецПопытки;
КонецФункции
Проанализируем этот подход: он очень надежен, но требует создания копии всей коллекции. Если в вашем Соответствии десятки тысяч элементов, преобразование типов создаст заметную нагрузку на память и процессор. Используйте этот метод только для небольших объемов данных.
Если вам нужно гарантированно проверить наличие ключа, не меняя состав коллекции и не создавая ее копий, остается старый добрый цикл. Несмотря на то, что это кажется медленным, для коллекций до 100-200 элементов разница будет неощутима.
Посмотрим на пример кода:
КлючНайден = Ложь;
Для Каждого Элемент Из МоеСоответствие Цикл
Если Элемент.Ключ = ИскомыйКлюч Тогда
КлючНайден = Истина;
Прервать;
КонецЕсли;
КонецЦикла;
Этот метод универсален, так как он корректно работает с любыми типами ключей (ссылками, числами, датами) и не имеет "побочных эффектов".
Если вы сами проектируете систему и знаете, что вам часто придется проверять наличие ключей, рассмотрите вариант хранения "обернутых" значений. Вместо того чтобы вставлять значение напрямую, вставляйте его внутрь вспомогательного объекта (например, Структуры).
Разберем пример:
// Вместо: Соответствие.Вставить("МойКлюч", Неопределено);
// Делаем так:
ЗначениеДляХранения = Новый Структура("Значение", Неопределено);
Соответствие.Вставить("МойКлюч", ЗначениеДляХранения);
// Теперь проверка становится тривиальной:
Результат = Соответствие.Получить("МойКлюч");
Если Результат = Неопределено Тогда
Сообщить("Ключа нет");
Иначе
Сообщить("Ключ есть, его значение: " + Результат.Значение);
КонецЕсли;
Этот подход — самый "чистый" с точки зрения архитектуры, так как он полностью устраняет неопределенность поведения метода Получить().
Выясним, какой метод выбрать в зависимости от ситуации:
Количество()).Для каждого).ФиксированноеСоответствие — смело используйте конструкцию Попытка...Исключение.Помните, что 1С — это гибкая платформа, и понимание внутренней механики объектов (таких как хеш-таблицы в Соответствии) позволяет писать не только работающий, но и высокопроизводительный код.