简单介绍了方法/函数参数的概念。在本文中,我们将深入探讨该主题的所有变体。如前所述,方法可以在没有参数的情况下工作,但通常它们会有一个或多个参数,这将有助于方法完成其任务。
我们在上一篇文章中已经看到了一个非常简单的参数使用场景:我们的 AddNumbers() 方法,它以两个数字作为参数,并返回这两个数字的和:
这在很大程度上展示了一个聪明的概念方法是什么,因为使用方法,您可以将功能封装在方法中,但能够在通过参数调用此方法时影响结果:
可选参数
默认情况下,当调用带有一个或多个参数的方法时,您必须为所有这些参数提供值。但是,在某些情况下,您可能需要将一个或多个参数设为可选。在某些编程语言中,您可以简单地通过将参数标记为可选来做到这一点,但在 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);