Методы
Фрагмент программного кода, к которому можно обратиться из другого места программы, называется методом. Данный раздел содержит особенности описания и использования методов.
Общая информация
При разработке программы часто возникает необходимость повторного использования какого-либо ее фрагмента. Для простого повторения одного и того же фрагмента кода несколько раз можно использовать цикл. А если этот фрагмент вызывается из разных мест программы, необходимо выделить программный код и оформить его специальным образом. Такой обособленный фрагмент кода называется метод. Метод может как возвращать значение, так и не возвращать ничего (подробнее).
Используемые определения
Для описания методов будут использоваться следующие термины:
-
Определение метода
Место в исходном коде, где создан метод.
-
Сигнатура метода
Уникальный идентификатор метода, состоящий из области видимости метода, модификатора, имени метода, списка параметров и типа возвращаемого значения.
-
Объявление метода
Часть определения метода, которая содержит его сигнатуру.
-
Имя метода
Идентификатор, расположенный после ключевого слова
метод, используемый для его объявления и вызова. -
Тело метода
Инструкции, расположенные между объявлением метода и символом
;, описывающим завершение метода. -
Вызов метода
Место в тексте программы, где указывается имя метода с указанием фактических параметров и использованием результата работы, если метод возвращает значение.
-
Определение параметров и передача аргументов
- Параметр метода — это именованная переменная, через которую тело метода получает значение при вызове.
- Аргумент — это значение, которое указывается во время вызова метода.
- Передача параметров — это предоставление аргументов методу при его вызове.
-
Возврат значения
Передача вызывающему программному коду какого-либо значения при завершении работы метода. Метод не обязательно должен возвращать какое-либо значение.
Определение метода
Прежде чем использовать метод, его надо определить. Определение метода выглядит следующим образом:
@аннотация-видимости
метод имя-метода([параметр[, параметр[, ...]]])[: тип-возвращаемого-значения]
тело-метода
[возврат[ значение]]
;
Каждый параметр описывается следующим образом:
имя-параметра: тип-параметра[ = значение-по-умолчанию]
имя-параметра задает имя параметра в соответствии со стандартными правилами формирования имен.
тип-параметра указывает, соответственно, тип параметра (в том числе и составной).
значение-по-умолчанию является необязательным элементом описания параметра и позволяет указать значение по умолчанию, т. е. значение, которое будет присвоено параметру, если при вызове метода соответствующий аргумент пропущен. Не допускается пропускать аргумент, если для соответствующего параметра не указано значение по умолчанию. В качестве значения по умолчанию выступает любое выражение, которое может быть вычислено на этапе компиляции:
- Литералы (включая литералы коллекций) являются вычислимыми.
- Обращения к константам модулей являются вычислимыми.
конст МЕТРОВ_В_КИЛОМЕТРЕ: Число = 1000
// Вычислимые значения по умолчанию: литерал и константа
метод МетровВКилометре(Сообщение: Строка = "В километре ", Число: Число = МЕТРОВ_В_КИЛОМЕТРЕ): Строка
возврат Сообщение + Число + " метров"
;
Операции над вычислимыми значениями являются вычислимыми, кроме следующих исключений:
- вызовы методов и обращения к свойствам,
- вызовы конструкторов.
конст МЕТРОВ_В_КИЛОМЕТРЕ: Строка = "1000"
// Выражение невозможно вычислить на этапе компиляции (вызов метода)
метод СообщитьЧасовойПояс(Пояс: ЧасовойПояс = ЧасовойПояс.Текущий()): Строка
возврат "Текущий часовой пояс $Пояс"
;
// Выражение невозможно вычислить на этапе компиляции (вызов конструктора)
метод МетровВКилометре(Число: Число = новый Число(МЕТРОВ_В_КИЛОМЕТРЕ)): Строка
возврат "В километре $Число метров"
;
Особенности передачи параметров
Переменные хранят ссылки на экземпляры. В методы передаются значения переменных. Таким образом можно в методе изменять состояние экземпляра, но нельзя изменить значение переменной, передаваемого в качестве аргумента метода.
метод ТестовыйМетод(Параметр: Число)
Параметр = 6
;
метод Скрипт(): Строка
пер ЧислоДляТеста = 5
ТестовыйМетод(ЧислоДляТеста)
пер ПослеВызова = "ДляТеста = $ЧислоДляТеста"
возврат ПослеВызова // ДляТеста = 5
;
В данном примере значение переменной ЧислоДляТеста в методе Скрипт() будет равно 5 и до вызова метода ТестовыйМетод(), и после вызова этого метода. Такое поведение обеспечивает передача параметра «по значению».
Однако, если в метод передается экземпляр, который располагает методами для изменения своего содержимого, то ситуация будет немного другой. Рассмотрим пример:
метод ОчиститьМассив(Параметр: Массив<Число>)
Параметр.Очистить()
// Не вызовет изменений
Параметр = [1, 2, 3]
;
метод Скрипт(): Строка
пер МассивДляТеста = [1, 2]
ОчиститьМассив(МассивДляТеста)
пер ПослеВызова = "ДляТеста = " + МассивДляТеста
возврат ПослеВызова // ДляТеста = []
;
В этом примере до выполнения метода ОчиститьМассив() в массиве МассивДляТеста будет два значения: 1 и 2. После вызова метода ОчиститьМассив() в массиве не будет ни одного элемента. В то же время, если в методе ОчиститьМассив() мы попробуем присвоить параметру Параметр какое-либо значение, это не приведет к каким-либо изменениям аргумента (переменной МассивДляТеста).
Вызов метода с именованными параметрами
При вызове метода допускается передача не только аргументов в соответствии с их позицией в сигнатуре, то есть позиционных аргументов, но и передача аргументов вместе с их именами — именованных аргументов. Например:
метод Поделить(А: Число, Б: Число): Число
возврат А / Б
;
метод Скрипт()
// Передача аргументов по именам
Поделить(Б = 10, А = 0) // 0
Поделить(А = 10, Б = 0) // Ошибка: деление на ноль
;
Синтаксис:
имя-метода(имя-параметра-1 = значение, имя-параметра-2 = значение, ... , имя-параметра-N = значение)
Нумерация параметров в данном случае является условной, используется для отличия параметров друг от друга и не указывает на порядковый номер параметра при определении или при вызове метода.
Прежде всего, могут быть методы, которые не допускают использования позиционных параметров. В этом случае перед ними указывается аннотация @ИменованныеПараметры. Подробнее об аннотациях смотрите здесь.
Кроме того, в некоторых случаях данный способ параметризации имеет ряд преимуществ перед использованием позиционирования:
-
Передавать аргументы не обязательно в строгом порядке. Пользователю не требуется искать сигнатуру метода, чтобы вспомнить строгий порядок аргументов. Кроме того, если во время написания кода разработчик поменяет местами параметры в сигнатуре, в том числе параметры одного типа, это не приведет к ошибке во время компиляции или исполнения кода. Сравните:
метод Разделить(А: Число, Б: Число): Числовозврат А / Б;метод Скрипт()// Вызовы ниже равносильны:Разделить(3, 2) // 1.5Разделить(А = 3, Б = 2) // 1.5Разделить(Б = 2, А = 3) // 1.5;или:
метод РассчитатьСебестоимость(Цена: Число, Наценка: Число, Налог: Число): Числовозврат Цена - (Цена * Наценка / 100 + Цена * Налог / 100);метод Скрипт()// Позиционные параметры// Результат: -0.1. НеправильноРассчитатьСебестоимость(1, 10, 100)// Именованные параметры// Результат: 89. ПравильноРассчитатьСебестоимость(Налог = 1, Наценка = 10, Цена = 100); -
Имена параметров отображаются сразу же при вызове метода. В таком случае не требуется искать сигнатуру метода, чтобы вспомнить, какой параметр что означает. См. пример выше или:
// Сигнатура методаметод Сообщить(Сообщение: Строка, ПеревестиВВерхнийРегистр: Булево): Строкаесли ПеревестиВВерхнийРегистр == Истинавозврат Сообщение.ВВерхнийРегистр()иначевозврат Сообщение;;метод Скрипт()// Непонятно значение второго параметраСообщить("Привет, мир!", Ложь) // "Привет, мир!"// Значение Истина передается в параметр ПеревестиВВерхнийРегистрСообщить("Привет, мир!", ПеревестиВВерхнийРегистр = Истина) // ПРИВЕТ, МИР!; -
Можно выборочно указывать только некоторые, наиболее важные параметры. Это может быть особенно удобным, когда в методе используется много необязательных, «служебных» параметров. Для них будут использоваться значения по умолчанию, которые указываются при определении метода. Кроме того, для некоторых параметров уже могут быть указаны значения по умолчанию. Так, если для метода
РассчитатьСебестоимость()задать значения по умолчанию: для наценки —10(%), а для налога —1(%), то можно передавать только значение цены. Сравните:метод РассчитатьСебестоимость(Налог: Число = 1, Наценка: Число = 10, Цена: Число): Числовозврат Цена - (Цена * Наценка / 100 + Цена * Налог / 100);метод Скрипт()// Позиционные параметры// Ошибка в следующей строке: Параметр Цена не имеет значения по умолчаниюРассчитатьСебестоимость(100)// Именованные параметрыРассчитатьСебестоимость(Цена = 100) // Результат: 89;
Смешанная параметризация
При вызове метода можно смешивать позиционную и именованную формы параметризации. Например:
метод РассчитатьСебестоимость(Налог: Число = 1, Наценка: Число = 10, Цена: Число): Число
возврат Цена - (Цена * Наценка / 100 + Цена * Налог / 100)
;
метод Скрипт()
// Вызовы метода ниже равносильны
РассчитатьСебестоимость(Налог = 1, Наценка = 10, Цена = 100)
РассчитатьСебестоимость(1, 10, Цена = 100)
;
При смешанной параметризации действуют следующие правила:
- Если параметр со значением по умолчанию находится перед параметром с обязательным значением, то в вызове при опускании первого параметра требуется указать второй параметр в именованном виде.
- При вызове все позиционные параметры должны быть указаны до первого именованного параметра.
- Параметры со значениями по умолчанию, находящиеся в конце сигнатуры, можно опускать при вызовах.
метод РассчитатьСебестоимость(Налог: Число = 1, Наценка: Число, Цена: Число = 100): Число
возврат Цена - (Цена * Наценка / 100 + Цена * Налог / 100)
;
метод Скрипт()
// Параметры Налог и Цена можно опустить, при этом указав имя для обязательного параметра Наценка
РассчитатьСебестоимость(Наценка = 10)
// После указания именованного параметра все последующие параметры должны быть именованные
// В параметр Цена нельзя передать аргумент, не указав его имя
РассчитатьСебестоимость(Наценка = 10, Цена = 100)
;
Разрешение неоднозначности
Неоднозначность в контексте вызова методов — это ситуация, когда невозможно однозначно определить, какую версию перегруженного метода следует выполнить при использовании именованных аргументов. При разрешении неоднозначности действуют следующие правила:
-
Если под вызов в именованной форме подходит несколько методов, выдается ошибка неоднозначности:
метод Обработать(А: Число, Б: Строка): Строкавозврат"Версия 1: ${А}, ${Б}";метод Обработать(Б: Строка, А: Число): Строкавозврат "Версия 2: ${А}, ${Б}";метод Скрипт()// Позиционный вызов — работаетОбработать(10, "текст") // Версия 1: 10, текст// Именованный вызов — ошибка неоднозначностиОбработать(А = 10, Б = "текст"); -
При объявлении методов проверка на возможную неоднозначность вызовов в именованной форме не производится, в отличие от позиционной:
// Проверка на возможную неоднозначность вызовов в именованной форме не производитсяметод Тест(Б: Строка, А: Число): Строкавозврат "Версия 1: ${А}, ${Б}";// Перегрузка метода Тест() допускает неоднозначный вызов в позиционной формеметод Тест(А: Число, Б: Строка): Строкавозврат "Версия 1: ${А}, ${Б}";метод Тест(А: Число, Б: Строка): Строкавозврат "Версия 1: ${А}, ${Б}"; -
Некоторые перегрузки метода не допускают использования именованной формы:
метод Конкатенация(А: Число, Б: Строка): Строкавозврат А.ВСтроку() + Б;метод Конкатенация(Б: Строка, А: Число): Строкавозврат А.ВСтроку() + Б;метод Скрипт()// Неоднозначный вызов метода Конкатенация() с именованными параметрамиКонкатенация(А = 10, Б = "текст"); -
При этом из нескольких перегрузок, которые соответствуют именованному вызову метода, будет выбрана перегрузка, у которой меньше всего аргументов:
// Первая перегрузкаметод Сообщить(Число: Число, Сообщение = "Это число: ", ПосчитатьДлину = Истина): Строкаесли ПосчитатьДлину == Истинавозврат "Длина сообщения: ${(Сообщение+Число).Длина()}"иначевозврат Сообщение + " " + Число;;// Вторая перегрузкаметод Сообщить(Число: Число, ПосчитатьДлину: Булево): Строкаесли ПосчитатьДлину == Истинавозврат "Длина сообщения: ${Число.ВСтроку().Длина()}"иначевозврат Число.ВСтроку();;// Какая перегрузка будет выбрана?метод Скрипт()// Первая — у второй есть обязательный параметр УдалитьПробелыСообщить(255)// Вторая — используется позиционная форма вызоваСообщить(255, Истина)// Первая — в сигнатуре второй перегрузки второй параметр имеет тип Булево, а не СтрокаСообщить(255, "Это число: ")// Первая — только она имеет все три подходящих параметраСообщить(255, "Это число: ", Истина)// Вторая — она короче, ошибки неоднозначности нетСообщить(Число = 255, ПосчитатьДлину = Ложь);
Завершение метода и возвращаемое значение
Метод завершает свою работу после того, как завершается исполнение тела метода.
метод ОчиститьМассив(Массив: Массив<Объект>)
// Метод завершит работу после очистки массива
Массив.Очистить()
;
Другой способ прервать исполнение метода — это указать в необходимом месте метода ключевое слово возврат.
метод ПроверитьЧетность(Число: Число): Строка
если Число == 0
// Выполнение метода прервется при передаче 0
возврат "Ноль не считается"
;
если Число % 2 == 0
// Выполнение метода прервется, если число четное
возврат "Четное"
;
// Выполнение метода завершится, если число нечетное
возврат "Нечетное"
;
метод Скрипт()
ПроверитьЧетность(0) // "Ноль не считается"
ПроверитьЧетность(4) // "Четное"
ПроверитьЧетность(7) // "Нечетное"
;
Если метод должен вернуть какое-либо значение, необходимо это значение указать в качестве параметра ключевого слова возврат. При этом тип фактически возвращаемого значения должен совпадать с типом возвращаемого значения, который указан при описании метода. Тип возвращаемого значения указывается после окончания списка формальных параметров метода после символа двоеточия. Если метод ничего не возвращает, можно использовать в качестве типа возвращаемого значения метода ключевое слово ничто (подробнее).
метод ОчиститьМассив(Массив: Массив<Объект>): ничто
// Метод ничего не возвращает
Массив.Очистить()
;
Для методов, смысл которых заключается в возврате значений, можно использовать аннотацию @ПроверятьИспользованиеЗначения. Если метод помечен этой аннотацией и возвращаемое им значение не используется, компилятор выдает ошибку. Пример:
@ПроверятьИспользованиеЗначения
метод СгенерироватьЧисло(): Число
возврат 42
;
метод Тест(): Число
// Ошибка компиляции: возвращаемое значение не используется
СгенерироватьЧисло()
// Правильное использование — значение присваивается
пер Число = СгенерироватьЧисло()
возврат Число
;
Перегрузка методов
При разработке может возникнуть необходимость создать метод, который в зависимости от задачи мог бы принимать разное количество или различные типы параметров. При этом выполняемое действие также зависело бы от их количества или типов. Такое поведение можно реализовать, например, используя составные типы параметров или значения параметров по умолчанию, но специально для таких задач существует перегрузка или повторное определение метода. Другими словами, такой способ можно рассматривать как описание нескольких методов с одним и тем же именем, но разными сигнатурами и особенностями реализации. Метод, который имеет несколько объявлений с разным составом параметров, будет называться перегруженным. При этом нужно помнить, что любой вызов перегруженного метода в исходном коде должен позволять компилятору однозначно определить, какой вариант метода необходимо использовать в данном случае. Если определить используемый вариант метода во время компиляции не представляется возможным, произойдет ошибка компиляции. Перегруженные методы не могут различаться только типом возвращаемого значения.
В том случае, если требуется выполнить перегрузку обработчика событий, следует учитывать следующее:
- Не поддерживается перегрузка обработчиков событий, которые имеют фиксированные имена (например,
ПриЗаписи,ПриОткрытиии т. д.).
Две перегрузки метода считаются конфликтующими, если одновременно выполняются следующие условия:
-
Одинаковое количество параметров — оба метода принимают одинаковое число параметров.
-
Совпадение типов параметров на одинаковых позициях — типы параметров в соответствующих позициях полностью совпадают. При этом тип
неизвестносчитается эквивалентным типуОбъект?. -
Совместимость способов вызова — выполняется одно из следующих условий:
- оба метода могут быть вызваны в позиционной форме,
- имена всех обязательных параметров одного метода являются именами параметров другого метода.
метод Перегрузка(Параметр: Число)
// Разные типы параметров
;
метод Перегрузка(Параметр: Объект)
//
;
метод Перегрузка(Параметр1: Число)
// Одинаковые типы параметров и их количество
;
метод Перегрузка(Параметр2: Число)
//
;
метод Перегрузка(Параметр1: Число)
// Разное количество параметров
;
метод Перегрузка(Параметр1: Число, Параметр2 = 0)
//
;
метод Перегрузка(Параметр1: неизвестно)
// Одинаковые типы параметров и их количество
;
метод Перегрузка(Параметр2: Объект?)
//
;
метод Перегрузка(Параметр1: Число, Параметр2: Число)
// Второй метод использует именованные параметры,
// и имена параметров различаются
;
@ИменованныеПараметры
метод Перегрузка(Параметр1: Число, Параметр3: Число)
//
;
метод Перегрузка(Параметр1: Число, Параметр2: Число)
// Одинаковые типы параметров и их количество
;
метод Перегрузка(Параметр1: Число, Параметр3: Число)
//
;
Перегруженные методы не могут отличаться только типом возвращаемого значения:
метод Обработать(Данные: Строка): Строка
возврат "Передан тип ${Данные.ПолучитьТип()}"
;
// Перегрузка метода Обработать() допускает неоднозначный вызов
метод Обработать(Данные: Строка): Число
возврат новый Число(Данные)
;
Также стоит отметить несколько особенностей, касающихся перегруженных методов:
- Основное назначение перегрузки метода — это удобство разработчика.
- Если описываемый алгоритм изначально ориентирован на то, что в качестве параметров передаются составные типы, не нужно выполнять перегрузку такого метода.
- Если алгоритм, описываемый методом, отличается от типа переданного параметра, то стоит рассмотреть возможность перегрузки метода, реализующего такой алгоритм.
- Перегруженными могут быть не только методы, но и конструкторы.
Алгоритм выбора перегрузки в момент вызова
При вызове перегруженного метода «1С:Шина» использует алгоритм разрешения перегрузок для определения наиболее подходящего варианта с определением глубины иерархии типа и максимальной глубины общей иерархии двух типов:
- Глубина иерархии типа — это максимальное расстояние от корня иерархии (тип
Объект) до типа по числу промежуточных наследников. - Максимальная глубина общей иерархии двух типов — это максимальная глубина иерархии у типов, являющихся общими предками обоих типов.
Если в момент вызова метода исходя из типов аргументов и их имен (при именованном вызове) подходит несколько перегрузок метода, то перегрузка выбирается по следующим правилам:
-
Выбирается наиболее специфичная перегрузка по максимальной глубине общей иерархии типов параметров и аргументов:
- Для каждого параметра перегрузки определяется максимальная глубина общей иерархии между типом параметра и типом переданного аргумента.
- Из этих значений выбирается минимальное значение максимальной глубины общей иерархии для каждой перегрузки.
- Перегрузка считается более специфичной, если ее минимальное значение больше, чем у других перегрузок.
-
Если после первого шага остается несколько равноценных перегрузок, выбирается та, которая имеет меньшее число обязательных параметров.
-
Если перегрузок нет или их больше одной — выдается ошибка неоднозначности.
Примеры разрешения перегрузок:
-
Базовые типы:
метод Перегрузка(Параметр1: Объект, Параметр2: Строка)// Перегрузка 1: менее специфичная;метод Перегрузка(Параметр1: Строка, Параметр2: Объект)// Перегрузка 2: менее специфичная;метод Перегрузка(Параметр1: Строка, Параметр2: Объект, Параметр3 = Истина)// Перегрузка 3: больше параметров;метод ВызватьПерегрузку()пер Объект: Объект = ""пер Строка: Строка = ""// Вызовется Перегрузка 1 (точное совпадение типов)Перегрузка(Объект, Строка)// Вызовется Перегрузка 2 (точное совпадение типов)Перегрузка(Строка, Объект)// Несовместимые типы: тип Объект не может быть присвоен в СтрокаПерегрузка(Объект, Объект)// Ошибка неоднозначности (обе перегрузки 1 и 2 подходят одинаково)Перегрузка(Строка, Строка); -
Составные типы в объявлениях:
метод Перегрузка(Параметр1: Строка|Число)// Перегрузка 1: точное совпадение для строк и чисел;метод Перегрузка(Параметр1: Дата|Число)// Перегрузка 2: точное совпадение для дат и чисел;метод Перегрузка(Параметр1: Объект)// Перегрузка 3: универсальная, менее специфичная;метод ВызватьПерегрузку()// Вызовется Перегрузка 1 (Строка входит в составной тип Строка|Число)Перегрузка("")// Вызовется Перегрузка 3 (логический тип ближе к Объект, чем к составным типам)Перегрузка(Истина)// Ошибка неоднозначности (подходят и Перегрузка 1, и Перегрузка 2)Перегрузка(22); -
Составные типы в объявлениях и в вызове:
метод Перегрузка(Параметр1: Массив<Строка>|ЧитаемаяКоллекция<Число>)// Перегрузка 1: специфичная для коллекций строк или чисел;метод Перегрузка(Параметр1: ЧитаемыйМассив<Объект>)// Перегрузка 2: более общая, для массивов объектов;метод ВызватьПерегрузку()пер МассивСтрокИлиМассивЧисел: Массив<Строка>|Массив<Число> = [2]пер МассивЧисел: Массив<Число> = [2]пер МассивСтрок: Массив<Строка> = [""]// Вызовется Перегрузка 2 (Массив<Число> ближе к ЧитаемыйМассив<Объект>, чем к ЧитаемаяКоллекция<Число>)Перегрузка(МассивСтрокИлиМассивЧисел)// Вызовется Перегрузка 1 (Массив<Строка> точнее соответствует Массив<Строка>|ЧитаемаяКоллекция<Число>)Перегрузка(МассивСтрок)// Вызовется Перегрузка 2 (Массив<Число> ближе к ЧитаемыйМассив<Объект>, чем к ЧитаемаяКоллекция<Число>)Перегрузка(МассивЧисел);