为什么静态在编译时隐藏被重写的方法

本文关键字:重写 方法 隐藏 静态 编译 为什么 | 更新日期: 2023-09-27 18:00:29

给定:

class BaseClass
{
    public virtual void M(int x) 
    { 
    }
}
class Derived : BaseClass
{
    public override void M(int x)
    {
        base.M(x);
    }
    static void M(object x)
    {
    }
    static void Main()
    {
        var d = new Derived();
        d.M(0);
    }
}

错误:

无法使用实例访问成员"Derived.M(object)"参考用类型名称而不是来限定它

查看C#4.0规范第7.4节(成员查找),第一个要点是:

在类型T中具有K个类型参数的名称N的成员查找是处理如下:

[…]包含重写修饰符的成员将从一组[名为N]的可访问成员

由此,我得出结论,覆盖Derived.M不再可访问。相反,编译器必须引用BaseClass.M

然而,这并不能解释为什么添加静态Derived.M会突然导致编译错误。编译器现在只能看到静态成员Derived.M,并得出该成员是无效调用的结论。如果删除静态Derived.M,则编译成功。

为什么会发生这种情况?

为什么静态在编译时隐藏被重写的方法

以下步骤似乎发生了,它们是编译器错误的原因:

  • 步骤1:因为7.4中的部分。您引用的,M的实例版本被删除,只剩下静态版本的编译器。静态的参数类型为object,与int兼容。方法名称也匹配
  • 步骤2:成员查找完成,因为已经找到了匹配的方法(正确的名称,兼容的参数),所以不需要向上继承链
  • 步骤3:仅现在检查该方法是实例方法还是静态方法

我真的不能引用规范中的一句话来证明这一点,但§7.4(成员查找)和§3.5(可访问性)都没有谈到static与实例的关系,所以我认为在进行成员查找时根本没有考虑到这一事实。

§7.4中的相关部分似乎是:

在类型T中具有K个类型参数的名称N的成员查找处理如下:

  • 首先,确定一组名为N的可访问成员:
    […]
    集合由T中名为N的所有可访问成员(§3.5)组成,包括继承成员和对象中名为N的可访问成员。如果T是构造类型,则通过替换§10.3.2中所述的类型自变量来获得成员集。包含替代修饰符的成员将从集合中排除

我理解这部分的方式就像上面解释的那样:它将返回实例方法和静态方法,然后删除实例方法,因为它有override修饰符
此时只剩下静态方法。

它的结尾是:

最后,[…],确定查找结果:
如果集合只包含方法,那么这组方法就是查找的结果。

因此,结果是静态方法。

显然,这个问题只发生在具有类层次结构并且其中一个派生类声明了具有相同名称和兼容参数的静态方法的情况下。

在现有类中添加这样一个静态方法的情况下,简单地向类中添加一些东西仍然是一个破坏性的更改

尽管我很确定你知道如何解决编译器错误,但我仍然会陈述它,以获得完整的答案:
使用任何基类作为变量的编译时类型。运行时类型仍然可以是派生类型,这不是问题所在:

BaseClass d          = new Derived();
// ^                         ^
// compile time type        runtime type

Daniel的回答是正确的;相关规则的简要概述是:

  • 派生程度较高的类中的方法总是被认为比派生程度较低的类中方法更好
  • 重写方法被认为是首先声明它们的类中的方法,而不是重写它们的类
  • 在检查接收器是否是实例并且方法是否是静态的之前,会解决哪个方法(如果有的话)是"最佳"的。(也在检查泛型类型参数约束之前。)

最后一条规则有点奇怪,但它确实有一些道理。关于这一特定规则的扩展讨论,请参阅我和尼科夫在精装带注释的C#4规范第290和291页上的评论。

此外,有关该规则与Color Color规则相交的有趣角落情况的分析,请参见

http://blogs.msdn.com/b/ericlippert/archive/2009/07/06/color-color.aspx