在方法重写中更改参数修饰符

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的重写中省略该关键字,则编译器会选择在两个方法中都装饰System.ParamArrayAttribute。我当然用IL DASM验证了这些事情。

我的问题:

这是文档化的行为吗? 我已经彻底搜查了C#语言规范,没有找到任何提及此事的内容。

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


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

2个回答

16
编译器的行为是正确的,但这有点混乱。我希望至少会有一个警告。
您找不到规范中它说这是正确的,这并不奇怪。相关部分如下:
方法调用形式M(A)的绑定时间处理,其中M是一个方法组,A是一个可选的参数列表,由以下步骤组成:构造方法调用的候选方法集。对于与方法组M关联的每个方法F,如果F是非泛型的,当M没有类型参数列表且F适用于A时,F是一个候选方法。
那么,“与方法组M关联的方法”是什么呢?首先,什么是方法组?
方法组是由成员查找产生的一组重载方法...
好的,那么成员查找规则是什么?
否则,集合包括T中名为N的所有可访问成员,包括继承的成员和object中名为N的可访问成员。 排除包含override修饰符的成员。
强调添加完毕。
在此情况下,实际影响是为了重载决策,覆盖的方法被认为是最初声明的方法,而不是覆盖的方法。不幸的是,在这种情况下违反了这个规则。
virtual void M(int x, int y) { }
...
override void M(int y, int x) { } 
...
M(x = 1, y = 2);

超载决策使用从更具体版本中获取的名称。这是一个不幸的结果,因为指定参数在很晚之后才被添加。简而言之:为了确定方法是否为“params”,分析是在原始方法上完成的,而不是在重写方法上完成的。编译器如果能够发出警告就好了。可以说至少Visual Studio开发环境对此感到困惑。IntelliSense层始终显示覆盖方法的方法信息,而不是被覆盖方法的信息。研究表明,当方法看起来像是原始声明方法而不是重写方法时,用户会感到困惑。当然,正如我之前提到的那样,这些是您将用于指定参数的参数名称。

2
请注意,这也解释了为什么它只能与接口一起使用(示例代码在http://stackoverflow.com/questions/27843804/compiling-generic-interface-vs-generic-abstract-class-params-keyword中重复)——没有“override”,因此带有`params`的方法*不被排除*在可能匹配项的列表之外。 - Alexei Levenkov

5
我认为这在C#规范的1.6.6.4段落中有所描述:
虚方法可以在派生类中被重写。当实例方法声明包括一个override修饰符时,该方法将覆盖具有相同签名的继承虚方法。而虚方法声明引入了一个新方法,覆盖方法声明则通过提供该方法的新实现来专门化现有的继承虚方法。 根据此,virtual方法声明在这里真的很重要。并且virtual方法声明在每次调用该方法时都会被使用。正确的override实现(如果指定)将在运行时采取,其中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);

它正常运作,没有任何问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接