22

The following F# code declares base and descendant classes. The base class has a virtual method 'Test' with a default implementaion. The descendant class overrides the base class method and also adds a new overloaded 'Test' method. This code compiles fine and presents no issues when accessing either of the descendant 'Test' methods.

F# Code:

module OverrideTest
  [<AbstractClass>]
  type Base() =
    abstract member Test : int -> int
    default this.Test x = x + 1

  type Descendant() =
    inherit Base()
    override this.Test x    = x - 1
    member this.Test (x, y) = x - y

However, attempting to invoke the descendant's override of 'Test' from C# results in a compilation error:

var result = td.Test(3); <- No overload for method 'Test' takes 1 arguments

The full C# Code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Client
{
  class Program
  {
    static void Main(string[] args)
    {
      var td = new OverrideTest.Descendant();
      var result = td.Test(3);
      Console.WriteLine(result);
      Console.ReadKey();
    }
  }
}

The strange thing is that VisualStudio's intellisense sees the two overloaded functions, and provides correct signatures for both. It gives no warnings or errors before the build fails, and only highlights the line afterwards.

I have re-implemented this scenario fully in C# and did not run into the same problem.

Anyone have any ideas what's going on here?

ildjarn
  • 62,044
  • 9
  • 127
  • 211
nxn
  • 3,975
  • 2
  • 20
  • 17
  • Have you compared the emitted IL in both languages? – Oded Nov 23 '11 at 16:53
  • What happens if in the C# code you upcast to the base class before calling the method? – Brian Nov 23 '11 at 16:59
  • 2
    The only weird thing I can see in the Reflector is that the base method shows as `override`, not `virtual`. It seems that's because the base method doesn't have a `newslot` attribute. – svick Nov 23 '11 at 17:07
  • 1
    I think this is a bug in either the C# compiler or in the F# compiler, but I'm not sure which one (my guess is it's C#). – svick Nov 23 '11 at 17:15
  • 2
    @svick - the lack of `newslot` on the `Base` method just means that if _its_ base class (`obj` in this case) were changed to have a virtual `Test` method with the right signature, `Base.Test` would override it rather than hiding it. I think that it would be better for F# to include `newslot` to avoid versioning problems, but I can't see why that should cause any problems in this situation. – kvb Nov 23 '11 at 17:47
  • 1
    This is probably a bug--we're going to look into it. Thanks for the report! Donna Malayeri (MSFT) – lindydonna Dec 12 '11 at 22:44
  • It's Sept 2013 now, and the issue is still there, with VS2012 and .NET 4.5. The only difference in IL now is that static member in C# has 'hidebysig' and in F# doesn't: F# .method public static bool Equals(string a, string b) cil managed C# .method public hidebysig static bool Equals(string a, string b) cil managed – Philip P. Sep 10 '13 at 15:15

1 Answers1

17

No doubt you're aware that if you omit the Test(x,y) member from the Descendant type -- or simply rename it Test2(x,y) -- then the C# code will compile and run as expected.

Looking at the IL generated for your original Descendant type offers a clue:

.method public hidebysig virtual
    instance int32 Test (
        int32 x
    ) cil managed ...

.method public 
    instance int32 Test (
        int32 x,
        int32 y
    ) cil managed ...

Notice that there's no hidebysig attribute on the Test(x,y) method.

The ECMA CLI specification has the following to say about hidebysig. (Section 15.4.2.2, emphasis in bold is mine.)

hidebysig is supplied for the use of tools and is ignored by the VES. It specifies that the declared method hides all methods of the base class types that have a matching method signature; when omitted, the method should hide all methods of the same name, regardless of the signature.

So, the F# compiler omits the hidebysig attribute, meaning that the Test(x,y) method hides all other methods named Test. Although hidebysig is only "for the use of tools", it appears that the C# compiler is one of those tools that uses it!

This looks to me like it could be a bug in the F# compiler, but since I've never looked at the F# spec it's always possible that this is allowed/specified behaviour.

LukeH
  • 263,068
  • 57
  • 365
  • 409