Каждый разработчик в экосистеме «1С:Предприятие» регулярно сталкивается с необходимостью поиска и обработки строк в табличных частях документов или справочников. Казалось бы, метод Найти() — это базовый и интуитивно понятный инструмент, который должен без проблем справляться с поиском по любой колонке. Однако при попытке выполнить поиск по стандартному системному реквизиту НомерСтроки разработчики часто сталкиваются с крайне странным поведением платформы: при поиске значения 0 метод вместо ожидаемого возврата значения Неопределено внезапно возвращает самую первую строку табличной части.
В этой статье мы подробно разберем внутренние механизмы платформы «1С:Предприятие», выясним техническую причину этой особенности, которая тянется еще со старых релизов платформы 1С 8.2, и рассмотрим наиболее быстрые и безопасные альтернативные методы получения строки по её номеру.
Рассмотрим классический пример кода, который часто пишут начинающие программисты, пытаясь перебрать или найти строку по её порядковому номеру:
Для Сч = -1 По 3 Цикл
Строка = ТабличнаяЧастьДокумента.Найти(Сч, "НомерСтроки");
Если Строка = Неопределено Тогда
Сообщить("При Сч = " + Сч + ", строка не найдена");
Иначе
Сообщить("При Сч = " + Сч + ", найдена строка " + Строка.НомерСтроки);
КонецЕсли;
КонецЦикла;
При выполнении этого цикла для значений -1, 1, 2 и 3 система отрабатывает штатно согласно синтакс-помощнику. Но как только переменная Сч принимает значение 0, метод возвращает ссылку на первую строку табличной части, хотя строки с номером 0 в табличной части физически существовать не может — нумерация в интерфейсе 1С всегда начинается с единицы!
Для того чтобы понять, почему это происходит, нам потребуется заглянуть «под капот» работы с коллекциями в платформе 1С.
Хотя реквизит НомерСтроки отображается в конфигураторе как стандартное поле табличной части любого документа, на уровне платформы он представляет собой виртуальное (вычисляемое) поле. Физически в оперативной памяти компьютера или в таблицах базы данных СУБД этот реквизит не хранится как отдельное индексируемое поле.
Значение реквизита НомерСтроки жестко привязано к реальному физическому индексу строки в памяти по формуле: НомерСтроки = Индекс + 1.
Когда мы вызываем метод прямого поиска по коллекции (такой как Найти()), платформа обращается исключительно к реальным физическим колонкам данных. Системное вычисляемое поле НомерСтроки не входит в этот список физических полей, поэтому для алгоритмов поиска платформы оно распознается как несуществующая колонка.
Поведение платформы при передаче неверного имени колонки в методах поиска различается:
НайтиСтроки() при передаче структуры отбора с несуществующим именем колонки сразу вызовет исключение («Неверное имя колонки») и аварийно завершит работу программы.Найти() спроектирован иначе. Если во втором параметре метода передано имя колонки, которую платформа не может сопоставить с физической структурой табличной части, этот параметр просто игнорируется. Программа при этом не выдает никаких ошибок и предупреждений.Когда имя колонки игнорируется, метод Найти() автоматически переходит в режим полнотекстового поиска значения по всем доступным колонкам табличной части.
Теперь по шагам разберем, что происходит в оперативной памяти компьютера при выполнении строки кода ТабличнаяЧастьДокумента.Найти(0, "НомерСтроки"):
"НомерСтроки" в табличной части нет.0 по всем колонкам таблицы.Количество, Сумма, Скидка, Коэффициент) по умолчанию инициализируются нулевым значением (0).0, метод фиксирует совпадение и мгновенно возвращает эту строку разработчику.Провести эксперимент и подтвердить это поведение очень просто. Если вы полностью заполните все числовые поля первой строки ненулевыми значениями (например, укажете количество 1, цену 100, скидку 5), то метод Найти() при поиске нуля вернет уже вторую строку табличной части. Если заполнить ненулевыми значениями абсолютно все числовые реквизиты во всех строках таблицы, метод наконец-то вернет значение Неопределено.
Важно провести четкую границу между поведением объектов ТабличнаяЧасть и ТаблицаЗначений:
ТабличнаяЧасть поле НомерСтроки виртуально, и прямой поиск по нему методом Найти() невозможен по причинам, описанным выше.Выгрузить(), платформа создаст физическую копию данных в памяти. В полученном объекте ТаблицаЗначений колонка "НомерСтроки" автоматически преобразуется в реальную физическую колонку типа Число. В таком объекте поиск вида ТЗ.Найти(1, "НомерСтроки") отработает абсолютно корректно и вернет нужную строку.Поскольку использовать метод Найти() для поиска строки по её номеру в табличной части некорректно, рассмотрим правильные и безопасные способы решения этой задачи.
Поскольку физический индекс строки в коллекции всегда равен НомерСтроки - 1, самым эффективным способом является обращение к строке по её индексу в квадратных скобках. Чтобы избежать фатальной ошибки «Индекс находится за границами диапазона» при обращении к несуществующей строке, предварительно выполним проверку общего количества строк в коллекции:
ИскомыйНомер = 2; // Номер строки, которую мы хотим получить
Если ИскомыйНомер > 0 И ИскомыйНомер <= ТабличнаяЧастьДокумента.Количество() Тогда
Строка = ТабличнаяЧастьДокумента[ИскомыйНомер - 1];
Сообщить("Успешно получена строка: " + Строка.НомерСтроки);
Иначе
Строка = Неопределено;
Сообщить("Строка с таким номером отсутствует в табличной части!");
КонецЕсли;
Метод Получить() также осуществляет доступ к элементу коллекции по его индексу (начиная с 0). Этот способ аналогичен первому, но требует явного вызова метода. Проверка границ коллекции здесь также строго обязательна:
ИндексСтроки = ИскомыйНомер - 1;
Если ИндексСтроки >= 0 И ИндексСтроки < ТабличнаяЧастьДокумента.Количество() Тогда
Строка = ТабличнаяЧастьДокумента.Получить(ИндексСтроки);
Иначе
Строка = Неопределено;
КонецЕсли;
Используя прямое обращение по индексу вместо метода Найти(), мы не только избавляемся от скрытых багов платформы с поиском нулевых значений, но и значительно увеличиваем скорость работы алгоритма. Прямой доступ к элементу в памяти по его индексу происходит мгновенно (за константное время O(1)), в то время как метод Найти() последовательно перебирает все строки и колонки таблицы (за время O(N*M)), неоправданно расходуя ресурсы процессора.