4

I have A.Test() declared as public virtual and B.Test() declared as private new.
I'm calling base.Test() from C that inherits B.

This code compiles with Mono 2.10.2 but throws a MethodAccessException:

class A {
    public virtual void Test () { }
}

class B : A {
    private new void Test () { }
}

class C : B {
    public C ()
    {
        base.Test ();
    }

    public static void Main (string[] args)
    {
        var c = new C ();
    }
}

Here is the exception I get:

System.MethodAccessException: Method TestBug.B:Test () is inaccessible from method TestBug.C:.ctor ()

Is this the correct behavior?

Does this compile in Microsoft .NET or with newer versions of Mono?
What does C# spec say about this?
Does it vary with C# version?

Dan Abramov
  • 264,556
  • 84
  • 409
  • 511

1 Answers1

12

It's valid C#, but the Mono 2.10.2 compiler is apparently doing the wrong thing. With the MS compiler, the call to base.Test() is compiled to:

IL_0008:  ldarg.0
IL_0009:  call       instance void A::Test()

The Mono 3.0.6.0 compiler works the same way.

As far as A is concerned, B.Test() effectively doesn't exist.

In fact, section 3.7 of the C# 5 spec even gives an explicit example which is very similar to yours:

A declaration of a new member hides an inherited member only within the scope of the new member.

class Base
{
    public static void F() {}
}

class Derived: Base
{
    new private static void F() {}   // Hides Base.F in Derived only
}

class MoreDerived: Derived
{
    static void G() { F(); }         // Invokes Base.F
}

In the example above, the declaration of F in Derived hides the F that was inherited from Base, but since the new F in Derived has private access, its scope does not extend to MoreDerived. Thus, the call F() in MoreDerived.G is valid and will invoke Base.F.

I strongly suspect that the Mono 2.10.2 is blindly inserting a call to B.Test() - not because it sees the private method's existence, but just to ensure that "a base class method is called". As it happens, that goes badly at execution time. The choice about which base class method to call is an interesting one, as B could change between C's compile-time and execution time, to override Test()... at which point the behaviour is non-obvious. Eric Lippert talks about this in a blog post which you may find interesting.

Community
  • 1
  • 1
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    @Jasmine: Because `B.Test()` is private. It's not even visible to `C`; `C` doesn't know it exists, so it calls the method it *does* know about. – Jon Skeet Apr 30 '13 at 21:44
  • @Jon, thanks for coming by! This is really really useful because I can't upgrade to Mono 3 at the moment. I guess in my case the quick fix would be to change `base.Test ()` to `this.Test ()` (`C` doesn't override `Test` so it wouldn't matter anyway). – Dan Abramov Apr 30 '13 at 21:56
  • 1
    @DanAbramov: It depends - if that compiles as a call to `B.Test()` then it would still be broken :( You could use `A a = this; a.Test();` instead though. Or if it's an option, *compile* with the MS compiler even though you're running with the Mono one. – Jon Skeet Apr 30 '13 at 21:59
  • @Jon: It doesn't throw an exception, so I guess calling `this.Test()` worked after all. If you're curious, here's the [relevant Rx code](https://github.com/stampsy/rx-monotouch/compare/6a96...5cf0). – Dan Abramov Apr 30 '13 at 22:31
  • @DanAbramov: Note that by changing it to `this.Dispose()`, you'll end up with *different* behaviour if that class ever overrides (or declares a `new` method) for `Dispose` itself. Personally I'd rather say, "You've got to build Rx with a working compiler." – Jon Skeet May 01 '13 at 06:00
  • @Jon Makes sense in the long run. I plan to soon switch to Mono 3 anyway—just waiting for bug-free MonoTouch support. Thanks for helping out. – Dan Abramov May 01 '13 at 07:36
  • @Jon I read the article you posted and still cannot see why the runtime behavior is non-obvious apart from a C# implementation detail? Why should a call to Derived.F() not be resolved as a call to Base.F() when the outside world calls the function rather than trying to resolve to Derived's private "hiding" of the method? That behaves an awful lot like private inheritance in C++ and breaks the "is-a" relationship between Base and Derived (a Derived can no longer be treated as a Base object if some version of the F() function made public by Base is not callable by the outside world). – statueuphemism May 01 '13 at 21:28
  • @statueuphemism: A call to `Derived.F()` *is* resolved as a call to `Base.F()` (by a correct compiler). That's the point. – Jon Skeet May 01 '13 at 21:46
  • @Jon My apologies, I misread the statement I have bolded as being part of the same vein of the sentences preceding it: "The choice about which base class method to call is an interesting one, as B could change between C's compile-time and execution time, to override Test()... **at which point the behaviour is non-obvious**" – statueuphemism May 01 '13 at 22:00