C# 入门教程 Method方法参数

简单介绍了方法/函数参数的概念。在本文中,我们将深入探讨该主题的所有变体。如前所述,方法可以在没有参数的情况下工作,但通常它们会有一个或多个参数,这将有助于方法完成其任务。

我们在上一篇文章中已经看到了一个非常简单的参数使用场景:我们的 AddNumbers() 方法,它以两个数字作为参数,并返回这两个数字的和:

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

这在很大程度上展示了一个聪明的概念方法是什么,因为使用方法,您可以将功能封装在方法中,但能够在通过参数调用此方法时影响结果:

AddNumbers(2, 3);
Result: 5
AddNumbers(38, 4);
Result: 42

这是参数的基本类型,但让我们更多地讨论可用于更改参数行为的所有各种修饰符和选项。

请注意,在本文中,我们将深入探讨所有类型的参数以及它们如何帮助您,但如果您刚开始使用 C# 并且只想查看一些结果,以下可能是现在有点太复杂和技术性了。随意跳过本文的其余部分,并在您准备好后返回。

可选参数

默认情况下,当调用带有一个或多个参数的方法时,您必须为所有这些参数提供值。但是,在某些情况下,您可能需要将一个或多个参数设为可选。在某些编程语言中,您可以简单地通过将参数标记为可选来做到这一点,但在 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 修饰符来允许任意数量的参数。它可能是这样的:

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# 和其他编程语言在两种参数类型之间有所不同: “按值” “按引用” 。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 修饰符允许该方法处理作为参数而不是副本传入的实际值。

输出修饰符

就像 ref 修饰符一样 out 修饰符 确保参数通过引用而不是通过值传递,但有一个主要区别:使用 ref 修饰符时,您传入一个初始化值,您可以选择在方法内部修改该值,或保持原样。另一方面,当使用 out 修饰符时 ,您被迫在方法内部初始化参数。这也意味着您可以在使用 out 修饰符时传入未初始化的值 - 如果您试图离开带有 out 参数的方法而不为其分配值,编译器会抱怨。

在 C# 中,一个方法只能返回一个值,但是如果您使用 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:

正如你在例子中的开头看到的,我宣布这两个变量( addedValue subtractedValue 将它们作为输出参数之前)。这是以前版本的 C# 语言的要求,但从 C# 版本 6 开始,您可以直接在方法调用中声明它们,如下所示:

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

in 修饰符

就像 out 修饰符一样 in 修饰符 确保将参数作为引用而不是值的副本传递,但与 out 修饰符不同的是, in 修饰符将阻止您对方法内的变量进行任何更改。

您的第一个想法可能是:如果我无法更改参数的值,那么我不妨将其作为常规参数传入,更改不会影响原始变量。你是对的,结果看起来完全一样,但仍然有一个非常有效的理由使用 in 修饰符:通过将它作为引用而不是值传递,你节省了资源,因为框架没有在将对象像常规参数一样传递给方法时,必须花时间创建对象的副本。

在大多数情况下,这不会有太大区别,因为大多数参数都是简单的字符串或整数,但是在循环中多次重复调用相同方法或传入大值参数的情况下,例如非常大字符串或结构,这可能会给您带来不错的性能提升。

这是我们使用 in 修饰符的示例:

public void Main(string[] args)
	string aVeryLargeString = "Lots of text...";
	InMethod(aVeryLargeString);
public void InMethod(in string largeString)
	Console.WriteLine(largeString);

在我们的方法,InMethod()时, largeString 参数现在是一个只读参考原始变量( aVeryLargeString ),因此我们可以使用它,但不能修改它。如果我们尝试,编译器会抱怨:

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

命名参数

正如您在上面的所有示例中看到的,每个参数在方法声明中都有一个唯一的名称。这允许您引用方法内的参数。但是,在调用该方法时,您不使用这些名称——您只需按照声明的顺序提供值。这对于需要 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);

这是使用命名参数的完整示例:

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);