为什么静态在编译时隐藏了重写方法?

时间:2013-02-21 16:01:12

标签: c#

假设:

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,那么编译成功。

为什么会这样?

2 个答案:

答案 0 :(得分:7)

以下步骤似乎发生并且是编译器错误的原因:

  • 步骤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节中所述。包含覆盖修饰符的成员将从集合中排除。
  •   

我理解这部分的方式如上所述:它将返回实例方法和静态方法,然后删除实例1,因为它具有override修饰符。
此时只剩下静态方法。

它以此结束:

  

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

因此,结果是静态方法。

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

将这样的静态方法添加到现有类是一种简单地向类添加内容仍然是重大更改的情况。

虽然我很确定你知道如何解决编译器错误,但我仍然会说出来,要有一个完整的答案:
使用任何基类作为变量的编译时类型。运行时类型仍然可以是派生类型,这不是问题:

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

答案 1 :(得分:4)

丹尼尔的回答是正确的;相关规则的简要摘要是:

    更多派生类中的
  • 方法总是被认为比较少派生类中的方法更好
  • 覆盖方法被认为是类中首先声明它们的方法,而不是覆盖它们的类
  • 解决哪个方法(如果有)“最佳”发生之前检查以查看接收方是否是实例且方法是静态的。 (并且在检查泛型类型参数约束之前。)

这最后一条规则有点奇怪,但确实有道理。有关此特定规则的扩展讨论,请参阅我的注释和nikov对精选注释C#4规范的第290和291页的评论。

此外,对于此规则与Color Color规则相交的有趣角点情况的分析,请参阅

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