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

时间:2015-12-30 19:08:40

标签: c# syntax language-design

考虑这样的方法

    public void updateDSField<T>(string fieldName, T newVal)
    {   
        // updates field in dataset

        var col = TheDataTable.Columns[fieldName];
        foreach (DataRow row in TheDataTable.Rows) row[col] = newVal; // * this marks the rows to be update by the commandtext

        // create adapter and update string
        TheAdapter.UpdateCommand = TheConn.CreateCommand();
        string newValString = newVal.ToString();
        if (typeof(T) == typeof(string)) newValString = "'" + newValString + "'";
        TheAdapter.UpdateCommand.CommandText = "UPDATE [" + TheTableName + "] SET [" + fieldName + "] =" + newValString;

        TheAdapter.Update(TheDS);  // this will only update those rows marked by step * above
    } 

我喜欢在调用它们时明确命名这样的方法的参数,因为人们很容易混淆void RegisterUser(string firstname, string lastname, int age); firstname参数。但是,lastname并不是必需的。例如,我认为从清晰的角度来看,这应该没问题。

age

但是会抛出以下错误:

  

在指定了所有固定参数后,必须出现命名参数规范

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

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

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

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

为什么C#设计得像这样?如果允许第一个场景,是否存在编译器的复杂性?

6 个答案:

答案 0 :(得分:14)

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

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

你怎么解释这个,25岁的年龄和史密斯的名字?为它制定规则,编译器可以实现它。但对人类来说有什么意义呢。代码混淆不应该那么容易

语言应该很难犯错误,而不是更容易

注意:如果之前的参数稍后被命名,则排序会发生奇怪的事情。 (就像我的例子中的名字和史密斯一样),因为这会成为你未命名的参数映射到正确参数的难题。它可以完成,但代码不应该产生谜题

答案 1 :(得分:6)

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

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

然后说:

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

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

如果没有命名你的论据,你基本上就是说你严格遵循函数定义所设置的结构。

答案 2 :(得分:2)

预期用途,包括:

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!

会让人感到困惑。

我可以看到你建议在特定的调用中保持位置排序,然后为了清楚起见,仅包括一些参数的名称。但是,似乎这种用法不是语言设计者的优先考虑。

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

答案 3 :(得分:1)

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

答案 4 :(得分:1)

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

假设我的方法是:

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

我称之为

Dimensions(3, length: 12, 24);

在这种情况下:

  

&#39; 3&#39;是第一个参数,指的是身高,但是&#39; 24&#39;是第三个参数,指的是长度,但我们已经指定了长度值。

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

另外,如果我们定义可选参数,最后提供位置参数可能会导致错误的结果。

答案 5 :(得分:0)

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

使用您的示例

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

并将其称为

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

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

<强> 25

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

<强>姓名:

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

在成功映射它的第一个命名参数后,顺序映射的跟踪已被破坏。因此,现在为编译器提供非命名参数是不可行的,因为它现在无法确定映射它的位置。

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

<强>名字:

对于命名的论据来说,这是一件轻而易举的事。

希望这会有所帮助:)