使用两次时的 C# params 参数选择

C# params argument selection when used twice

我很好奇为什么以下两个 DoInvoke 方法都不能只用一个参数调用:

public class foo {
    private void bar(params object[] args) {
        DoInvoke(args);
    }

    //Error: There is no argument given that corresponds to the required formal parameter 'args' of 'foo.DoInvoke(Delegate, object[])'
    private void DoInvoke(Delegate d, object[] args) {
        d.DynamicInvoke(args);
    }

    //Error: Argument 1: cannot convert from 'object[]' to 'System.Delegate'
    private void DoInvoke(Delegate d, params object[] args) {
        d.DynamicInvoke(args);
    }
}

我已经找到了一种不滥用参数的方法。我很好奇为什么这里没有展开params。

我能够在 Lua 中做类似的事情,因此我尝试了。我知道 Lua 远没有那么严格,但我不确定这样做违反了哪条 C# 规则。

I'm curious why neither of the following DoInvoke methods can be called with only one params:

简短版本:第一个不能,因为它有两个非可选参数,因为您为第一个非可选参数传递了错误类型的值范围。第二个不能,但这只是因为您尝试为单个非可选参数传递的值类型错误;第二个参数是可选的,因此可以像您所做的那样省略。


您的印象似乎是,在您的方法声明 private void bar(params object[] args) 中,params 关键字的存在使 args 变量在某种程度上不同于任何其他变量。不是。 params 关键字影响 调用站点,允许(但不是 要求 )调用者指定 args 变量被指定为单独的参数,而不是显式创建数组。

但即使您以这种方式调用 bar(),也会像传递任何其他数组一样创建一个数组对象并将其传递给 bar()bar() 方法中的变量 args 只是一个数组。它没有得到任何特殊处理,并且编译器不会(例如)隐式地将它扩展为参数列表以用于传递给其他方法。

我不熟悉 Lua,但这与 C/C++ 中的可变参数函数有些不同,后者的语言提供了一种将可变参数列表传播到更下方的被调用方的方法.在 C# 中,可以直接传播 params 参数列表的唯一方法是被调用方是否可以接受调用方中声明的确切数组类型(由于 C# 中的数组类型差异,并不总是必须是完全相同的类型,但仍然有限)。


如果您好奇的话,相关的 C# 语言规范在很多地方都解决了这个问题,但主要是在 “7.5.1.1 相应参数” 中。上面写着(来自 C# 5 规范……有一个 C# 6 规范草案,但 C# 5 基本相同,我有一份):

For each argument in an argument list there has to be a corresponding parameter in the function member or delegate being invoked.

它继续描述 "parameter list" 用于验证参数列表的内容,但在您的简单示例中,在应用此规则时已经发生重载解析,因此只有一个参数要担心的清单:

• For all other function members and delegates there is only a single parameter list, which is the one used.

接着说:

The corresponding parameters for function member arguments are established as follows:
• Arguments in the argument-list of instance constructors, methods, indexers and delegates:
    o A positional argument where a fixed parameter occurs at the same position in the parameter list corresponds to that parameter. [emphasis mine]
    o A positional argument of a function member with a parameter array invoked in its normal form corresponds to the parameter array, which must occur at the same position in the parameter list.
    o A positional argument of a function member with a parameter array invoked in its expanded form, where no fixed parameter occurs at the same position in the parameter list, corresponds to an element in the parameter array.
    o A named argument corresponds to the parameter of the same name in the parameter list.
    o For indexers, when invoking the set accessor, the expression specified as the right operand of the assignment operator corresponds to the implicit value parameter of the set accessor declaration.

换句话说,如果您不在参数列表中提供参数名称,则参数按位置对应于方法参数。并且您调用的两个方法的第一个位置的参数的类型为 Delegate.

当您尝试调用第一个方法时,该方法有 个可选参数,但您没有提供第二个参数。所以你得到一个错误告诉你你的参数列表,只包含一个参数(上面对应于 Delegate d 参数),不包括对应于 object[] args 的第二个参数被调用方法中的参数。

即使您提供了第二个参数,您也会 运行 陷入与尝试调用第二个方法示例时相同的错误。 IE。虽然 params object[] args 参数是可选的(编译器将为调用提供一个空数组),因此您可以在对该方法的调用中只提供一个参数,但该参数的类型错误。它的位置对应于 Delegate d 参数,但您试图传递类型 object[] 的值。没有从 object[]Delegate 的转换,因此调用失败。


那么,这对实际代码意味着什么?嗯,这取决于你想做什么。当您尝试将 args 变量传递给 void DoInvoke(Delegate d, params object[] args) 方法时,您 期望 发生什么?

一个明显的可能性是 args 数组包含一个 Delegate 对象作为其第一个元素,数组的其余部分是要传递的参数。在这种情况下,您可以这样做:

private void bar(params object[] args) {
    DoInvoke((Delegate)args[0], args.Skip(1).ToArray());
}

对于您展示的任何一种 DoInvoke() 方法,这在语法上应该是有效的。当然,这是否真的是您想要的还不清楚,因为我不知道调用的目的是什么。