Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does a static hide an overridden method at compile time?

Tags:

c#

Given:

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);
    }
}

Error:

Member 'Derived.M(object)' cannot be accessed with an instance reference; qualify it with a type name instead

Looking at the C# 4.0 spec section 7.4 (Member Lookup) the first bullet point reads:

A member lookup of a name N with K type parameters in a type T is processed as follows:

[...] Members that include an override modifier are excluded from the set [of accessible members named N]

From this I conclude that the override Derived.M is no longer accessible. Instead, the compiler must refer to BaseClass.M.

However, this does not explain why adding a static Derived.M suddenly causes a compilation error. The compiler can now only see the static member Derived.M and concludes that this member is an invalid call. If I remove the static Derived.M then compilation succeeds.

Why does this happen?

like image 838
P.Brian.Mackey Avatar asked Dec 21 '22 09:12

P.Brian.Mackey


2 Answers

The following steps seem to happen and are together the reason for your compiler error:

  • Step 1: Because of the part from 7.4. you quoted, the instance version of M is removed, leaving the compiler only with the static one. The parameter type of the static one is object, which is compatible to int. The method name matches, too.
  • Step 2: Member lookup finished, because a matching method has been found (correct name, compatible parameters), no need to go up the inheritance chain.
  • Step 3: Only now is checked if the method is an instance method or a static one.

I can't really quote a single sentence of the spec proving this, but neither §7.4 (Member lookup) nor §3.5 (Accessability) talk about static vs. instance, so I assume this fact is simply not taken into account at all when doing a member lookup.

Relevant parts from §7.4 seem to be:

A member lookup of a name N with K type parameters in a type T is processed as follows:

  • First, a set of accessible members named N is determined:
    [...]
    The set consists of all accessible (§3.5) members named N in T, including inherited members and the accessible members named N in object. If T is a constructed type, the set of members is obtained by substituting type arguments as described in §10.3.2. Members that include an override modifier are excluded from the set.

The way I understand this part it is like explained above: It would return both the instance method and the static one and will then remove the instance one, because it has the override modifier.
Only the static method is left at this point.

It closes with this:

Finally, [...], the result of the lookup is determined:
if the set contains only methods, then this group of methods is the result of the lookup.

So, the result is the static method.

Obviously, this problem only happens in circumstances where you have a class hierarchy and one of the derived classes declares a static method with the same name and compatible parameters.

Adding such a static method to an existing class is a case where simply adding something to a class is still a breaking change.

Although I am pretty sure you know how to solve the compiler error, I will still state it, to have a complete answer:
Use any of the base classes as the compile time type of your variable. The runtime type still can be the derived type, that's not the problem:

BaseClass d          = new Derived();
// ^                         ^
// compile time type        runtime type
like image 86
Daniel Hilgarth Avatar answered Jan 19 '23 01:01

Daniel Hilgarth


Daniel's answer is correct; a brief summary of the relevant rules is:

  • methods in a more derived class are always considered better than methods in a less-derived class
  • overriding methods are considered to be methods in the class which first declares them, not the class which overrides them
  • resolving which method, if any, is "best" happens before the check to see if the receiver is an instance and the method is static. (And also before generic type parameter constraints are checked.)

This last rule is a bit odd, but it does make some sense. See my comments and nikov's comments on pages 290 and 291 of the hardbound annotated C# 4 specification for an extended discussion of this specific rule.

Also, for an analysis of an interesting corner case where this rule intersects the Color Color rule, see

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

like image 32
Eric Lippert Avatar answered Jan 19 '23 00:01

Eric Lippert