16

我知道params修饰符(将数组类型的一个参数转换为所谓的“参数数组”)特别不是方法签名的一部分。现在考虑这个例子:

class Giraffid
{
    public virtual void Eat(int[] leaves)
    {
        Console.WriteLine("G");
    }
}
class Okapi : Giraffid
{
    public override void Eat(params int[] leaves)
    {
        Console.WriteLine("O");
    }
}

这编译没有警告。然后说:

var okapi = new Okapi();
okapi.Eat(2, 4, 6);  // will not compile! 

给出一个错误(No overload for method 'Eat' takes 3 arguments)。

现在,我知道编译器会将params修饰符转换为System.ParamArrayAttribute相关参数的应用程序。通常,将一个属性集合应用于虚拟方法的参数,然后在派生类的覆盖方法中用不同的属性集修饰“对应”参数是没有问题的。

然而编译器选择params默默地忽略我的关键字。相反,如果反过来,将其应用于params基类中的参数Giraffid,然后在覆盖中省略关键字,Okapi编译器会选择用. 当然,我用 IL DASM 验证了这些东西。System.ParamArrayAttribute

我的问题:

这是记录在案的行为吗?我已经彻底搜索了 C# 语言规范,但没有发现任何提及这一点。

我可以说至少 Visual Studio 开发环境对此感到困惑。2, 4, 6在上述方法调用中键入时,智能感知会在提示中显示void Okapi.Eat(params int[] leaves)


params为了比较,我还尝试实现一个接口方法并更改接口和实现类中的存在/不存在,我尝试定义一个委托类型并params在委托类型定义或我分配给其方法组的方法中更改或不更改我的委托类型的变量。在这些情况下,完全有可能改变params-ness。

4

2 回答 2

16

编译器的行为是正确的,但这有点乱。我希望这至少是一个警告。

毫不奇怪,您在规范中找不到它说这是正确的地方。相关位是:

M(A) 形式的方法调用的绑定时处理,其中 M 是方法组,A 是可选参数列表,包括以下步骤: 构造方法调用的候选方法集. 对于与方法组 M 关联的每个方法 F,如果 F 是非泛型的,则当 M 没有类型参数列表时,F 是候选方法,并且 F 适用于 A。

什么是“与方法组 M 相关的方法”?好吧,首先,什么是方法组?

方法组,它是由成员查找产生的一组重载方法...

好的,那么成员查找规则是什么?

否则,该集合由 T 中名为 N 的所有可访问成员组成,包括继承成员和对象中名为 N 的可访问成员。包含覆盖修饰符的成员将从集合中排除。

重点补充。

这里的实际结果是,出于重载决议的目的,被覆盖的方法被认为是最初声明的方法,而不是被覆盖的方法。 不幸的是,在这种情况下违反了这条规则:

virtual void M(int x, int y) { }
...
override void M(int y, int x) { } 
...
M(x = 1, y = 2);

重载解析使用来自更派生版本的名称。这是在游戏后期添加命名参数这一事实的不幸结果。

简而言之:为了确定方法是否为“参数”,分析是在原始方法上完成的,而不是在覆盖方法上。

如果编译器在这里给你一个警告,那就太好了。

可以说至少Visual Studio开发环境对此感到困惑

正确的。IntelliSense 层始终显示重写方法的方法信息,而不是重写方法。研究表明,当方法看起来像是最初的声明方法而不是覆盖方法时,用户会感到困惑。当然,正如我之前提到的,这些是您将用于命名参数的参数名称。

于 2013-04-02T21:38:20.727 回答
5

我认为它在 c# 规范的 1.6.6.4 段中有所描述:

可以在派生类中重写虚方法。当实例方法声明包含覆盖修饰符时,该方法会覆盖具有相同签名的继承虚拟方法。虚拟方法声明引入了新方法,而覆盖方法声明通过提供该方法的新实现来专门化现有继承的虚拟方法。

据此,virtual方法声明在这里非常重要。每次调用该virtual方法时都会使用方法声明。正确override的 n 个实现(如果指定)是在运行时进行的,与此params无关。

可以通过简单的测试来确认:

class Giraffid
{
    public virtual void Eat(params int[] leaves)
    {
        Console.WriteLine("G");
    }
}
class Okapi : Giraffid
{
    public override void Eat(int[] leaves)
    {
        Console.WriteLine("O");
    }
}

有了那个宣言

var o = new Okapi();
o.Eat(1, 2, 3);

100% 正常工作。

于 2013-04-02T21:32:06.743 回答