Почему Неопределено не может быть ключом Соответствия в 1С и как с этим работать

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

В практике разработки на платформе 1С:Предприятие 8 часто возникает необходимость сопоставить значения различных типов с какими-либо представлениями или результатами обработки. Для этих целей идеально подходит объект Соответствие (Map). Однако программисты часто сталкиваются с неожиданным поведением системы, когда пытаются использовать в качестве ключа значение Неопределено. Разберем подробно, почему так происходит, к каким логическим коллизиям это приводит и как правильно обрабатывать такие ситуации.

Суть проблемы: Неопределено в коллекции Соответствие

Рассмотрим классическую ситуацию. Программист хочет создать карту соответствий для перечисления, где также должна быть обработана «пустая» или неопределенная ссылка. На первый взгляд код выглядит логично:


СоответствиеПредставления = Новый Соответствие;
СоответствиеПредставления.Вставить(Перечисления.Статусы.ПустаяСсылка(), "?");
СоответствиеПредставления.Вставить(Неопределено, "?"); // Вот здесь кроется подвох
СоответствиеПредставления.Вставить(Перечисления.Статусы.Активен, "+");

Проблема заключается в том, что при попытке вставить Неопределено в качестве ключа, платформа 1С не выдает ошибку исключения. Код выполняется успешно, но фактическая запись в объект Соответствие не добавляется. Если мы проверим количество элементов методом Количество(), мы увидим, что запись с ключом Неопределено была проигнорирована.

Анализ внутренней реализации и хэширования

Выясним причину такого поведения. Объекты Структура и Соответствие в 1С являются хэш-структурами. Для эффективного поиска система должна вычислить хэш-код для ключа. Для большинства типов данных (строки, числа, даты, ссылки) этот механизм отлажен. Однако тип Неопределено по своей сути означает отсутствие какого-либо типа или значения.

Проанализируем ситуацию: для генерации хэша объект должен поддерживать сравнение и иметь уникальный идентификатор типа. Неопределено — это единственное значение своего типа, и разработчики платформы, судя по всему, решили не включать его в поддержку механизма ключей для хэш-таблиц. При этом NULL (значение из базы данных) в некоторых версиях платформы может вести себя иначе, но также не рекомендуется к использованию в качестве ключа из-за особенностей сравнения NULL <> NULL.

Логическая коллизия метода Получить()

Рассмотрим более глубокую причину, почему Неопределено в роли ключа создало бы путаницу. Обратимся к синтаксису метода Получить() объекта Соответствие:

Синтаксис: Получить(<Ключ>)

Возвращаемое значение: Возвращает значение элемента или Неопределено, если указанный ключ отсутствует в коллекции.

Теперь представим, что платформа разрешила бы нам использовать Неопределено как ключ и хранить по нему, например, строку "Пусто". В этом случае возник бы парадокс:

  1. Если мы вызываем Соответствие.Получить("НесуществующийКлюч"), мы получаем Неопределено.
  2. Если мы вызываем Соответствие.Получить(Неопределено), мы ожидаем получить "Пусто".

Однако, если бы мы хранили само значение Неопределено внутри соответствия, мы бы никогда не смогли понять: нам вернулся результат (потому что такой ключ есть) или нам вернулся признак отсутствия ключа в коллекции. Чтобы избежать этой двусмысленности, использование Неопределено в качестве ключа заблокировано на уровне движка платформы.

Экспериментальная проверка

Разберем на примере кода, как ведут себя разные ключи. Проведем тест, который наглядно показывает отсутствие записи:


ТестСоответствие = Новый Соответствие;
ТестСоответствие.Вставить(1, "Число один");
ТестСоответствие.Вставить(Неопределено, "Это не вставится");
ТестСоответствие.Вставить(NULL, "Результат непредсказуем");

Сообщить("Всего элементов: " + ТестСоответствие.Количество()); // Вернет 1 или 2, но не 3
Сообщить("Поиск Неопределено: " + ТестСоответствие.Получить(Неопределено)); // Всегда вернет Неопределено (как признак отсутствия)

Как мы видим, попытка вставки не вызывает критического сбоя приложения (Runtime Error), что можно считать своего рода «защитой от дурака», чтобы бизнес-логика не прерывалась при случайном попадании незаполненного значения в алгоритм формирования коллекции.

Где Неопределено работает как значение?

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

В других коллекциях, которые не используют механизм хэширования для поиска, таких как ТаблицаЗначений или СписокЗначений, Неопределено работает полноценно. Посмотрим на пример с таблицей значений:


Отбор = Новый Структура("Результат", Неопределено);
Строки = ТвояТаблица.НайтиСтроки(Отбор);
// Этот код отработает корректно и найдет все строки, где значение не определено.

Способы решения и обхода

Проанализируем ситуацию и выберем лучшие практики для обхода этого ограничения:

  1. Использование строковых маркеров. Если вам нужно сопоставить «пустоту» с чем-то, используйте уникальную строку, которая гарантированно не встретится в обычных данных.
    
    КлючДляПустых = "___INTERNAL_UNDEFINED___";
    МоеСоответствие.Вставить(КлючДляПустых, "?");
    // При поиске:
    ИскомыйКлюч = ?(Значение = Неопределено, КлючДляПустых, Значение);
    Результат = МоеСоответствие.Получить(ИскомыйКлюч);
    
  2. Предварительная проверка ЗначениеЗаполнено(). Вместо того чтобы пихать всё в соответствие, проверьте значение заранее.
    
    Если НЕ ЗначениеЗаполнено(Ключ) Тогда
        Результат = "?";
    Иначе
        Результат = СоответствиеПредставления.Получить(Ключ);
        Результат = ?(Результат = Неопределено, "-", Результат);
    КонецЕсли;
    
  3. Использование Тип("Неопределено"). В 1С само описание типа может быть ключом. Тип значения Неопределено — это объект типа Тип, и он вполне может быть ключом в Соответствии.
    
    МоеСоответствие.Вставить(ТипЗнч(Неопределено), "?");
    Результат = МоеСоответствие.Получить(ТипЗнч(ВходнаяПеременная));
    

Итоги

Помните, что Соответствие — это мощный инструмент для работы с произвольными типами, но Неопределено и NULL стоят в иерархии типов особняком. Проанализируйте свои алгоритмы на предмет попадания этих значений в ключи. Всегда проверяйте входящие параметры перед вставкой в коллекции, чтобы избежать логических ошибок и «проглатывания» данных, которые не попали в коллекцию из-за системных ограничений платформы.

← На главную