TOC

This article is currently in the process of being translated into Russian (~96% done).

Классы:

Method parameters

В предыдущей статье, мы рассказывали о методах и мы кратко затронули понятие "параметры метода/функции". В этой статье мы рассмотрим эту тему глубже со всеми возможными вариантами. Как упоминалось, методы могут работать и без параметров, но обычно они принимают один или более параметров, которые помогают методу осуществить его задачу.

Мы уже видели пример очень простого использования параметров в предыдущей статье: наш метод AddNumbers() принимает в качестве параметров 2 числа и возвращает сумму этих чисел.

public int AddNumbers(int number1, int number2)
{
	return number1 + number2;
}

Этот пример наглядно показывает, насколько концепция методов является удачной, поскольку применение методов позволяет скрыть в нём весь необходимый функционал, при этом у вас остается возможность влиять на результат его выполнения через параметры:

AddNumbers(2, 3);

Result: 5

AddNumbers(38, 4);

Result: 42

Такой тип параметров является базовым, поэтому давайте поговорим о всех доступных модификаторах и возможностях повлиять на поведение параметров.

Обратите внимание: в этой главе мы рассмотрим все возможные типы параметров и возможности их применения, но если вы только начинаете своё знакомство с языком C# и просто хотели ознакомиться с этой темой, всё написанное ниже может показаться слишком сложным и полным технических терминов. В этом случае можете спокойно пропустить оставшуюся часть этой главы и вернуться к ней, когда будете готовы.

Необязательные параметры

По умолчанию, при вызове метода с одним или несколькими параметрами, необходимо указывать значения для них всех. Однако, в некоторых ситуациях, может потребоваться сделать один или несколько параметров необязательными. В некотрых языках программирования такой эффект достигается путём указания ключевого слова optional рядом с параметром, но в C# параметр считается необязательным если для него было указано значение по-умолчанию при определении метода. Такой подход является более оптимальным, поскольку позволяет не писать допольнительный код для обработки ситуаций, когда вызывающий код не указал значение для такого параметра.

Ниже приведён пример метода с необязательным параметром:

public int AddNumbers(int number1, int number2, int number3 = 0)
{
	return number1 + number2 + number3;
}

Последний параметр (number3) теперь необязательный, поскольку для него установлено значение по-умолчанию (0). Таким образом, при вызове метода можно указывать два или три параметра:

public void Main(string[] args)
{
	AddNumbers(38, 4);
	AddNumbers(36, 4, 2);
}

Необязательных параметров может быть больше одного, таким образом, метод вообще может состоять только из одних необязательных параметров. Только помните, что необязательные параметры должны идти в конце списка параметров при определении метода, а не в начале или между обязательными параметрами.

Модификатор params

В качестве альтернативы использованию нескольких необязательных параметров, можно использовать ключевое слово params, позволяющее передать произвольное количество параметров. Взгляните на следующий пример:

public void GreetPersons(params string[] names) { }

Вызов функции будет выглядить следующим образом:

GreetPersons("John", "Jane", "Tarzan");

Дополнительное преимущество использования params заключается в том, что Вы также можете вызывать функцию с нулевым числом параметров, иначе говоря, вовсе без параметров. Функции с params также могут принимать обычные параметры, если параметр с модификатором params – последний. Кроме того, в функции разрешено использовать только один параметр с ключевым словом params. Ниже приведен пример использования модификатора params для вывода переменного количества имен посредством нашей функции GreetPersons():

public void Main(string[] args)
{
	GreetPersons("John", "Jane", "Tarzan");
}

public void GreetPersons(params string[] names)
{
	foreach(string name in names)
		Console.WriteLine("Hello " + name);
}

Способы передачи параметров: значение или ссылка

В C#, как и в других языках программирования, различаются два способа передачи параметров: "по значению" ("by value") и "по ссылке" ("by reference"). По умолчанию, параметры в C# передаются "по значению". Это означает, что когда вы передаете переменную в функцию, вы фактически передаете копию объекта, а не ссылку на него. Это также означает, что вы можете изменять значение параметра внутри функции, не затрагивая исходный объект, переданный в качестве параметра.

Следующий пример демонстрирует описанное поведение:

public void Main(string[] args)
{
	int number = 20;
	AddFive(number);
	Console.WriteLine(number);
}

public void AddFive(int number)
{
	number = number + 5;
}

Наша очень простая функция AddFive() прибавляет 5 к числу, переданному в нее. В функции Main() мы объявляем переменную number со значением 20 и вызываем функцию AddFive(). В следующей строке, когда мы выводим значение переменной number, кто-то, возможно, ожидает, что теперь ее значение равно 25, однако, оно по-прежнему 20. Почему? Потому что по умолчанию, параметр был передан как копия оригинального объекта (по значению), так что когда функция AddFive() работала с параметром, она использовала копию объекта и, следовательно, никак не влияла на исходную переменную.

Есть два варианта изменения метода AddFive(), чтобы он мог изменить значение исходного объекта: использовать модификатор ref или модификаторы in/out.

Модификатор ref

В английском языке ref это сокращение от "reference" (ссылка), и он просто изменяет передачу параметра по умолчанию по значению на передачу по ссылке, что означает, что теперь мы передаем ссылку на оригинальный объект вместо копии его значения. Вот измененный пример:

public void Main(string[] args)
{
	int number = 20;
	AddFive(ref number);
	Console.WriteLine(number);
}

public void AddFive(ref int number)
{
	number = number + 5;
}

Вы должны были заметить, что я добавил ключевое слово "ref" в два места: в объявление метода и когда передавал параметр при вызове метода. С этими изменениями мы получим поведение, которое могли ожидать изначально: результат теперь 25 вместо 20, поскольку модификатор ref позволяет методу работать с исходным объектом вместо его копии.

Модификатор out

Так же, как и модификатор ref, модификатор out обеспечивает передачу параметра по ссылке, а не по значению, но есть существенное различие: при использовании модификатора ref, вы передаёте инициализированное значение, которое вы можете изменить внутри метода, или оставить его в таком виде. С другой стороны, при использовании out модификатора необходимо инициализировать параметр внутри метода. Это также означает, что вы можете передавать неинициализированные значения при использовании модификатора out - компилятор будет жаловаться, если вы пытаетесь оставить метод с параметром out, не назначая ему значения.

В С метод может вернуть только одно значение, но если вы используете модификатор out, вы можете обойти его, передав несколько параметров с модификатором out - при вызове метода все параметры будут назначены. Вот пример, когда мы передаём два числа, а затем, используя модификаторы, возвращаем сложение и вычитание, используя эти числа:

public void Main(string[] args)
{
	int addedValue, subtractedValue;
	DoMath(10, 5, out addedValue, out subtractedValue);
	Console.WriteLine(addedValue);
	Console.WriteLine(subtractedValue);
}

public void DoMath(int number1, int number2, out int addedValue, out int subtractedValue)
{
	addedValue = number1 + number2;
	subtractedValue = number1 - number2;
}
Output:

15
5

Как вы можете видеть в начале примера, я объявляю две переменные ( addedValue и subtractedValue ) перед их передачей в качестве выходных параметров. Это было требованием в предыдущих версиях языка C#, но в C# версии 6 вы можете объявить их непосредственно в вызове метода, например:

DoMath(10, 5, out int addedValue, out int subtractedValue);
Console.WriteLine(addedValue);
Console.WriteLine(subtractedValue);

Модификатор In

Также как модификатор Out , модификатор In обеспечивает передачу параметров by reference, вместо копии самого значения, но в отличии от модификатора Out, модификатор In предотвращает возможность внесения каких либо изменений над переменной внутри самого метода.

Сперва вы должно быть подумали: если я не могу изменять значение параметра, следовательно, это тоже что и передача обычного параметра, где изменения не будут влиять на оригинальное значение. И вы правы, ожидаемый результат будет тот же, но все же есть веская причина использовать модификатор In: передавая его по ссылке вместо значения, вы сохраняете ресурсы, потому что framework не придется тратить время, чтобы создать копию объекта при передаче его в метод в качестве обычного параметра.

В большинстве случаев не будет значительной разницы, потому что большинство параметров это обычные строки или целые числа, но в случаях когда вы многократно вызываете метод в цикле или вы передаете параметры, содержащие большие значения, например, очень большие строки или структуры, это может дать вам отличное улучшение производительности.

Вот пример, где мы используем модификатор In:

public void Main(string[] args)
{
	string aVeryLargeString = "Lots of text...";
	InMethod(aVeryLargeString);
}

public void InMethod(in string largeString)
{
	Console.WriteLine(largeString);
}

In our method, InMethod(), the largeString parameter is now a read-only reference to the original variable (aVeryLargeString), so we're able to use it, but not modify it. If we try, the compiler will complain:

public void InMethod(in string largeString)
{
	largeString = "We can't do this...";
}

Error: Cannot assign to variable 'in string' because it is a readonly variable

Named parameters

Как вы видели во всех приведенных выше примерах, каждый параметр имеет уникальное имя в объявлении метода. Это позволяет ссылаться на параметр внутри метода. Однако, вызывая метод, вы не используете эти имена - вы просто даёте значения в том же порядке, в котором они объявлены. Это не является проблемой для простых методов, которые принимают 2-3 параметров, но некоторые методы более сложны и требуют больше параметров. В таких ситуациях может быть довольно трудно понять, к чему относятся различные значения в названии метода, как, например, в данном примере:

PrintUserDetails(1, "John", 42, null);

Не совсем ясно, что означают различные параметры в этом вызове метода, но если вы посмотрите на объявление метода, вы можете увидеть его:

public void PrintUserDetails(int userId, string name, int age = -1, List<string> addressLines = null)
{
	// Print details...
}

Но это раздражает, если вы постоянно ищете объявление метода, чтобы понять, что делают параметры, так что для более сложных методов, вы можете указать имена параметров непосредственно в вызове метода. Это также позволяет указать имена параметров в любом порядке, вместо того, чтобы использовать порядок объявления. Вот пример:

PrintUserDetails(name: "John Doe", userId: 1);

В качестве дополнительного бонуса, это позволит Вам предоставить значение для любого из Ваших факультативных параметров без необходимости предоставления значений для всех предыдущих необязательных параметров в объявлении. Другими словами, если вы хотите указать значение параметра addressLines в этом примере, вам также нужно будет указать значение параметра age, потому что он будет первым в объявлении метода. Однако, если вы используете именованные параметры, порядок больше не имеет значения и вы можете просто предоставить значения для требуемых параметров, а также один или несколько факультативных параметров, например, вот так:

PrintUserDetails(addressLines: new List<string>() { }, name: "Jane Doe", userId: 2);

Here's the complete example of using named parameters:

public void Main(string[] args)
{
	PrintUserDetails(1, "John", 42, null);
	PrintUserDetails(name: "John Doe", userId: 1);
	PrintUserDetails(addressLines: new List<string>() { }, name: "Jane Doe", userId: 2);
}

public void PrintUserDetails(int userId, string name, int age = -1, List<string> addressLines = null)
{
	// Print details...
	Console.WriteLine(name + " is " + age + " years old...");
}

Резюме

Как вы можете видеть из этой статьи, параметры бывают разных форм и типов. К счастью, вы будете использовать обычные, старые обычные параметры, но как только вы начнете копать глубже в язык C #, вам будет полезно знать все типы и модификаторы, как описано в этой статье.