TOC

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

Классы:

Method parameters

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

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

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

This goes a long way in showing what a clever concept methods is, because with methods, you can encapsulate the functionality in the method, but be able to affect the result when calling this method through the parameters:

AddNumbers(2, 3);

Result: 5

AddNumbers(38, 4);

Result: 42

This is the basic type of parameters, but let's talk more about all the various modifiers and options you can use to change the behavior of the parameters.

Please notice that in this article, we'll really dig into all the various types of parameters and how they can help you, but if you're just getting started with C# and you just want to see some results, the following might be a bit too complex and technical for now. Feel free to skip the rest of the article and return later when you're ready.

Optional parameters

By default, when calling a method with one or several parameters, you are forced to supply values for all of these parameters. However, in some situations, you may need to make one or several parameters optional. In some programming languages, you would be allowed to do that simply by marking the parameter as optional, but in C#, a parameter is made optional by supplying a default value for it in the method declaration. This is actually a nice solution, because it saves you from writing extra code to deal with situations where the parameter is not supplied by the caller.

Here's an example of a method with an optional parameter:

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

The last parameter (number3) is now optional, because we have provided a default value for it (0). When calling it, you can now supply two or three values, like this:

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

You can make more than one parameter optional - in fact, your method may consist of only optional parameters if needed. Just remember that optional parameters must come last in the method declaration - not first or in between non-optional parameters.

The params modifier

As an alternative to a number of optional parameters, you can use the params modifier to allow an arbitrary number of parameters. It could look like this:

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

Calling it could then look like this:

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

Another advantage of using the params approach, is that you are allowed to pass zero parameters to the method as well. Methods with params can also take regular parameters, as long as the parameter with the params modifier is the last one. Besides that, only one parameter using the params keyword can be used per method. Here is complete example where we use the params modifier to print a variable number of names with our GreetPersons() method:

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

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

Parameter types: value or reference

C#, and other programming languages as well, differ between two parameter types: "by value" and "by reference". The default in C# is "by value", which basically means that when you pass a variable to a method call, you are actually sending a copy of the object, instead of a reference to it. This also means that you can make changes to the parameter from inside the method, without affecting the original object you passed as a parameter.

We can easily demonstrate this behavior with an example:

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

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

We have a very basic method called AddFive() which will add 5 to the number we pass to it. So in our Main() method, we create a variable called number with the value of 20 and then call the AddFive() method. Now on the next line, when we write out the number variable, one might expect that value is now 25, but instead, it remains 20. Why? Because by default, the parameter was passed in as a copy of the original object (by value), so when our AddFive() method worked on the parameter, it was working on a copy and thereby never affecting the original variable.

There are currently two ways of changing this behavior, so that our AddFive() method is allowed to modify the original value: We can use the ref modifier or the in/out modifiers.

The ref modifier

The ref modifier is short for "reference" and it basically just changes the behavior of the parameter from the default behavior of "by value" to "by reference", meaning that we are now passing in a reference to the original variable instead of a copy of its value. Here's a modified example:

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

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

You will notice that I have added the "ref" keyword in two places: In the method declaration and when passing in the parameter in the method call. With this change, we now get the behavior we originally might have expected - the result is now 25 instead of 20, because the ref modifier allows the method to work on the actual value passed in as a parameter instead of a copy.

The out modifier

Just like the ref modifier, the out modifier ensures that the parameter is passed by reference instead of by value, but there's a major difference: When using the ref modifier, you pass in an initialized value which you may choose to modify inside the method, or leave it as it is. On the other hand, when using the out modifier, you are forced to initialize the parameter inside the method. This also means that you can pass in uninitialized values when using the out modifier - the compiler will complain if you are trying to leave a method with an out parameter without assigning a value to it.

In C#, a method can only return one value, but if you use the out modifier, you are able to circumvent this by passing in several parameters with the out modifier - when the method has been called, all out parameters will have been assigned. Here's an example, where we pass in two numbers and then, using out modifiers, return both an addition and a subtraction using these numbers:

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

As you can see in the beginning of the example, I declare the two variables (addedValue and subtractedValue) before passing them as out parameters. This was a requirement in previous versions of the C# language, but from C# version 6, you can declare them directly in the method call, like this:

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

The in modifier

Также как модификатор 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

As you have seen in all of the examples above, each parameter has a unique name in the method declaration. This allows you to reference the parameter inside the method. However, when calling the method, you don't use these names - you just supply the values in the same order as they are declared. This is not a problem for simple methods which takes 2-3 parameters, but some methods are more complex and requires more parameters. In those situations, it can be quite hard to figure out what the various values in a method call refers to, like in this example:

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

It's not very clear what the various parameters means in this method call, but if you look at the method declaration, you can see it:

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

But it's annoying if you constantly have to look up the method declaration to understand what the parameters are doing, so for more complex methods, you can supply the parameter names directly in the method call. This also allows you to supply the parameter names in any order, instead of being forced to use the declaration order. Here's an example:

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

As an added bonus, this will allow you to supply a value for any of your optional parameters, without having to supply values for all previous optional parameters in the declaration. In other words, if you want to supply a value for the addressLines parameter in this example you would also have to provide a value for the age parameter, because it comes first in the method declaration. However, if you use named parameters, order no longer matters and you can just supply values for the required parameters as well as one or several of the optional parameters, e.g. like this:

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 #, вам будет полезно знать все типы и модификаторы, как описано в этой статье.