命名参数的规则是什么?为什么?

What are the rules for named arguments and why?

考虑这样的方法

void RegisterUser(string firstname, string lastname, int age);

我喜欢在调用方法时像这样明确命名方法的参数,因为有人很容易混淆 firstnamelastname 参数。然而,对于 age 来说,这并不是真正必要的。例如,从清晰度的角度来看,我认为这应该没问题。

RegisterUser(firstname: "John", lastname: "Smith", 25);

但是会抛出以下错误:

Named argument specifications must appear after all fixed arguments have been specified

另一个有趣的事情是如果签名是

void RegisterUser(int age, string firstname, string lastname);

然后如下调用它不会抛出错误

RegisterUser(25, firstname: "John", lastname: "Smith");

为什么C#是这样设计的?如果允许第一种情况,编译器是否会复杂化?

编译器可能能够弄清楚,但对于我们人类来说,几乎不可能知道 25 是指第一个参数还是第三个参数。特别是因为它打开了混合参数的可能性。为什么不

MyFunction(firstname: "josh", 25, "smith", someotherargument: 42)

您如何解释年龄为 25,姓氏为 smith?为它制定规则,编译器可以实现它。但是对人类来说有意义的是什么。代码混淆不应该那么容易

一种语言应该让犯错变得困难,而不是更容易

注意:如果较早的参数后来被命名,那么奇怪的事情就会开始发生在排序上。 (就像我示例中的 firstname & smith),因为这会成为将未命名参数映射到正确参数的难题。可以做到,但代码不应产生谜题

这是因为当您命名参数时,编译器会根据名称映射它并忽略函数定义,只要所有必需的参数都存在。在你的情况下,它不知道 25 是什么。对我们来说,它必须是年龄似乎是合乎逻辑的,但是如果您将示例更改为:

void RegisterUser(string firstname, string lastname, int age = 0, int weight = 0);

然后说:

RegisterUser(firstname: "John", lastname: "Smith", 25);

然后编译器不知道如何处理最后的25。这种调用函数的方式主要用于具有很多默认值的函数,而您只想设置几个。

当你不命名你的参数时,你基本上是在说你严格遵循函数定义设置的结构。

所有命名参数都必须在位置参数之后;您无法在样式之间切换。位置参数始终引用方法声明中的相应参数。您不能通过稍后使用命名参数指定位置参数来跳过参数。编译器使用临时局部变量。然后它重新排序参数槽中的那些局部变量,我的猜测是编译器按顺序按参数绑定,直到找到命名参数,然后它丢弃它已经绑定但没有名称的参数,并在编译器使用临时局部变量时重新排序。按名称绑定其余部分,例如它将 25 与年龄绑定,然后重新排序名字:"John",姓氏:"Smith"

预期用途,具有:

void M(int a = -1, int b = -1, int c = -1, int d = -1, int e = -1);

例如,您可以通过位置表示法指定这些可选参数的子集:

M(42, 28, 101);  // gives a, b, and c in order; omits d and e

或者您可以使用命名参数:

M(d: 50, a: 42, c: 101);  // gives three arguments in arbitrary order

或者您可以组合它们,从位置参数开始,然后切换到命名参数:

M(42, 28, e: 65537, d: 50);  // mixed notation OK

您遇到的限制原因是:

M(c: 101, 7, 9, b: 28, 666);  // ILLEGAL!

会令人困惑。

我看到您建议在您的特定调用中保留位置顺序,然后为了清楚起见仅包含一些参数的名称。然而,这种用途似乎并不是语言设计者的优先考虑。

我建议你命名 所有 参数,因为你使用命名样式的原因是清楚的(而不是只需要指定参数的适当子集)。

我认为这种情况下的语言设计取决于任何函数的第一个命名参数。

使用你的例子

void RegisterUser(int age, string firstname, string lastname);

并调用它

RegisterUser(25, firstname: "John", lastname: "Smith");

在遇到第一个参数之前,编译器根本不知道这个函数是否要使用任何命名参数。因此,编译器进行顺序映射是一个安全的假设。

25:

编译器获取第一个参数,如果它是一个未命名的参数,那么它将立即映射到函数定义中的第一个参数。

名字:

一旦遇到它的第一个命名参数,事情就会发生变化,因为编译器现在必须检查函数定义中提到的所有剩余参数以映射当前命名参数。

成功映射它的第一个命名参数后,顺序映射的踪迹就被打破了。因此,现在向编译器提供非命名参数是不可行的,因为现在它无法确定将其映射到何处。

如果您认为它应该记住最后一个非命名参数并从那里开始顺序映射,那么这也很容易出错,因为刚刚定义的命名参数也可能是顺序正确的。

姓氏:

对于命名参数,这是轻而易举的事。

希望对您有所帮助:)

位置参数放在命名参数之前。 位置参数总是引用方法声明中的相应参数

假设我的方法是:

void Dimensions(int height, int breadth , int length);

我这样称呼它

Dimensions(3, length: 12, 24);

在这种情况下:

'3' is the first parameter and refers to height, but '24' is third parameter and refers to length, but we have already specified value of length.

所以也许为了克服这个障碍,默认情况下,c# 样式是在开始时定位 位置参数 以提供正确的引用。

此外,如果我们定义可选参数,在末尾提供位置参数可能会导致错误的结果。