Параметры
Необязательные и именованные параметры
Прим. Тот же Мартин утверждает, что использование необязательных параметров является антипаттерном, так как ухудшает читаемость кода. Я в этом с ним согласен, однако для общего образования стоит знать, как необязательные параметры реализуются.
Некоторым (или даже всем) параметрам метода можно присваивать значения по умолчанию, в результате чего их можно не передавать при вызове. Кроме того, при вызове метода существует возможность указать аргументы явно, воспользовавшись именами параметров.
Правила использования параметров
Определяя параметры по умолчанию, стоит руководствоваться следующими правилами:
- Значения по умолчанию можно указывать для методов, конструкторов и параметрических свойств. Также можно указать для параметров, являющихся частью делегатов.
- Параметры по умолчанию должны следовать за всеми остальными.
- Во время компиляции параметры по умолчанию должны оставаться неизменными, то есть они должны быть примитивных типов. Также ими могут быть перечисления и ссылочные типы, допускающие
null. Для значимого типа задаётся экземпляр с обнулёнными полями. В этом случае можно использоватьdefaultилиnew. - Нельзя переименовывать параметры по умолчанию, так как это вызовет ошибку компиляции при передаче по имени параметра.
- При вызове метода извне модуля параметр по умолчанию является потенциально опасным, так как его использует вызывающая сторона. И если не перекомпилировать вызывающий код при изменении исходного, это может спровоцировать ошибки.
- Для параметров с ключевыми словами
refиoutзначения по умолчанию не задаются.
Существуют дополнительные правила при вызове методов с необязательными или именованными параметрами:
- Аргументы могут идти в произвольном порядке, но именованные аргументы должны находиться в конце списка.
- Передача по имени возможна для параметров, не имеющих значения по умолчанию, при этом компилятору должны быть переданы все аргументы, необходимые для компиляции.
- В C# не могут отсутствовать аргументы. Если необходимо пропустить один параметр, стоит использовать именованные параметры.
Атрибут DefaultParameterValue и необязательные атрибуты
Если компилятор встречает вызов метода, где не хватает параметров, он проверяет, содержат ли они специальные флаги в метаданных о том, являются ли они необязательными.
Неявно типизированные локальные переменны
В C# поддерживается возможность определения типа используемых в методе локальных переменных по типу используемого при их инициализации выражения. Эта возможность реализована с применением ключевого слова var. Неявно типизированной переменной нельзя присвоить null, так как компилятор не в состоянии определить, какой ссылочный или нуллабильный значимый тип разработчик хочет использовать.
Ключевое слово var позволяет сократить код, так как если разработчик захочет поменять тип переменной, ему не придётся переписывать его дважды (слева и справа от оператора присвоения).
Данное ключевое слово бывает также полезно при работе с коллекциями через циклы или при получении значения из метода. Однако в случае изменения возвращаемого типа или типа коллекции весь остальной код может перестать компилироваться.
Передача параметров в метод по ссылке
По умолчанию CLR предполагает, что все параметры методов передаются по значению. При передаче объекта ссылочного типа методу передаётся ссылка на объект (прим. Причём передаётся именно копия ссылки. Внутри метода можно изменить поле объекта, ссылка на который передана, или изменить саму ссылку, чтобы она указывала на другой объект). Если в метод передаётся значимый тип, то передаётся его копия. Следует знать тип объекта, передаваемого в метод, чтобы понимать, что с ним может там произойти.
CLR также позволяет передавать объект по ссылке, а не по значению, с помощью ключевых слов ref и out. Оба слова заставляют компилятор генерировать метаданные, описывающие параметр, как предаваемый по ссылке. Компилятор использует эти метаданные для генерации кода, передающего вместо самого параметра его адрес (прим. В случае ссылочного типа ссылка не копируется, а передаётся та же самая).
С точки зрения CLR эти ключевые слова отличаются только одним битом в метаданных, который указывает, какое конкретно слово было использовано. Однако их различает компилятор при выборе метода, используемого для инициализации объекта. Если параметр помечен ключевым словом out, вызывающий метод не инициализирует его, пока не вызван сам метод. В этом случае вызванный метод не может прочитать значение параметра и должен инициализировать его, прежде чем вернуть управление. Если же использовано ключевое слово ref, то вызванный метод может как читать, так и изменять его параметр.
При передаче по ссылке значимого типа передаётся адрес экземпляра этого типа в стеке. Отличие ключевых слов, как и описывалось ранее, состоит в том, что out не обязан принимать инициализированный параметр и может его инициализировать внутри вызванного метода, а ref должен принимать инициализированную переменную, которую сможет прочитать и изменить. Использование ключевого слова out со значимыми типами может повысить производительность, так как предотвращается копирование экземплярного полей значимого типа.
С точки зрения IL или CLR данные ключевые слова не различаются. Они лишь помогают компилятору гарантировать корректность кода. Например, нельзя передать в ref неинициализированное значение.
В C# можно создать перегрузки методов, которые отличаются наличием или отсутствием этих ключевых слов. Но вот перегрузки методов, отличающиеся этими ключевыми словами, создать нельзя, так как результатом из JIT-компиляции будет идентичный код метаданных.
Со значимыми типами использование данных ключевых слов даёт тот же результат, что и передача ссылочного типа по значению. Они позволяют методу управлять единственным экземпляром значимого типа. Вызывающий код выделяет память для этого экземпляра, а вызванный метод управляет выделенной памятью. В случае ссылочных типов вызывающий код выделяет память для указателя передаваемый объект, а вызванный код управляет этим указателем. В связи с этим использование ключевых слов со ссылочными типами полезно, лишь когда метод собирается вернуть ссылку на известный ему объект.
Передача переменного количества аргументов
метод, принимающий переменное числа параметров, объявляют с применением ключевого слова params. Метод с этим словом работает так, как будто ему передают массив, однако предоставляет более удобный синтаксис.
Обнаружив вызов подобного метода, компилятор сначала проверяет все методы с заданным именем, у которых ни один из параметров не помеченным атрибутом ParamArray. Найдя такой метод - компилятор генерирует вызывающий его код. Иначе ищутся методы с данным атрибутом и проверяется, могут ли они принять вызов. Если компилятор находит подходящий метод, то перед генерацией вызова метода генерируется код, создающий и заполняющий массив.
Данным ключевым словом может быть помечен только последний параметр.
Для написания метода, принимающего произвольное число параметров любого типа, стоит объявлять параметры типом object.
Вызов метода с переменным числом параметров снижает производительность, так как необходимо выделить под объекты массива память в куче, а потом эту память очистить (если в качестве аргумента не передаётся null). Для оптимизации данной работы стоит определить несколько перегрузок методов, которые принимают, например, один, два или три параметра, а через ключевое слово params описывать более редкие кейсы.
Типы параметров и возвращаемых значений
Объявляя тип параметров метода, нужно по возможности указывать "минимальные" типы, предпочитая интерфейсы базовым классам. Связано это с тем, что минимальный тип может принять большее число потенциальных типов аргумента.
В то же время, объявляя тип возвращаемого объекта, желательно выбирать самый сильный из доступных вариантов. Это сделано потому, что вызывающему методу лучше рассчитывать на конкретный набор членов типа.
Если же требуется сохранить возможность изменять возвращаемый тип, то следует выбирать более слабый тип возвращаемого значения, чтобы в будущем не появилось необходимости редактировать и вызывающий код. То есть стоит выбирать самый "сильный" из доступных самых "слабых" типов.
Константность
В некоторых языках имеется возможность объявления методов и параметров как константы, запрещая тем самым в экземплярном методы изменять поля объекта или объекты, передаваемые в метод. В CLR (и, соответственно, в C#) такой возможности нет.
Создавая реализацию типа, разработчик может просто избегать написания кода, меняющего объекты и параметры.
