4

鉴于:

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 的成员查找处理如下:

[...] 包含 override 修饰符的成员被排除在 [名为 N 的可访问成员] 的集合中

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

但是,这并不能解释为什么添加静态Derived.M会突然导致编译错误。编译器现在只能看到静态成员 Derived.M并断定该成员是无效调用。如果我删除静态Derived.M然后编译成功。

为什么会这样?

4

2 回答 2

7

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

  • 第 1 步:由于 7.4 中的部分。你引用的实例版本M被删除,只留下编译器的静态版本。静态的参数类型是object,兼容int. 方法名称也匹配。
  • 第二步:成员查找完成,因为找到了匹配的方法(正确的名称,兼容的参数),不需要上继承链。
  • 第 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
于 2013-02-21T16:10:10.327 回答
4

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

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

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

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

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

于 2013-02-21T18:37:39.850 回答