Partager via


Visual Basic hides virtual methods from C#? Not really!

I was recently asked to investigate a potential issue about the C# to Visual Basic language inter-op. The punch line was that C# could not call a method defined in a library written in Visual Basic. However, if you used C# or Visual Basic for both assemblies, everything worked as expected.

The Visual Basic code used to produce the library is:

   1: Public MustInherit Class Base
  2:     Public Overridable Sub Print()
  3:     End Sub
  4: 
  5:     Public Overridable Sub Print(ByVal preview As Boolean)
  6:     End Sub
  7: End Class
  8: 
  9: 
 10: Public MustInherit Class Derived
 11:     Inherits Base
 12: 
 13:     Public Overrides Sub Print()
 14:     End Sub
 15: End Class

For those who are not proficient in Visual Basic the code means that Base is an abstract class (MustInherit) with two virtual (Overridable) methods called Print. The Derived class is also abstract and derives from Base. If defines a method that overrides the method Print (the one without any parameters).

Here is the C# code that references the library:

   1: public class MyClass : VBLibrary.Derived
  2: {
  3:     public void Foo()
  4:     {
  5:         Print(true);
  6:     }
  7: }

When you compile the C# code referencing the Visual Basic library you get this error:

CS1501: No overload for method 'Print' takes 1 arguments

Inspecting the VB library with ILDasm shows that both Print methods are emitted by the VB compiler. Since the methods are emitted in the library and when using C# to write the library the code compiles, let’s see if there is a difference between the code generated by the VB compiler and the one generated by the C# compiler.

The code generated by the Visual Basic compiler for the Derived.Print() method is this:

   1:  .method public virtual instance void Print() cil managed
  2:     {
  3:     }

while the code generated by the C# compiler is

   1:  .method public hidebysig virtual instance void Print() cil managed
  2:     {
  3:     }

Notice the extra hidebysig modifier on the method generated by the C# compiler?

According to the CLR ECMA Specifications (Section 15.4.2.2.) hidebysig - "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." In C# and VB the signature of a method consists of the method name and its parameter types.

This is the root cause of the problem.

When the C# compiler tries to bind the Print(true) call it first has to do a member lookup for the method Print. The simplified process steps are (read Section 7.3 of the C# spec for more info):

  1. Determine the list of all the methods named Print defined by MyClass and its base classes. While building this list, exclude all the methods that are marked override
  2. Remove all the members that are hidden by other members
  3. Return the list

Because the referenced library does not mark the Print() method as hidebysig the method will hide all the methods named Print defined on the Base class. And according to the member lookup algorithm they have to be excluded from the candidate set (step 2). This explains the error that the C# compiler gives.

So the behavior is by design. If you do want to be able to call both Print methods from C#, you have to write the VB code like this:

   1: Public MustInherit Class Derived
  2:     Inherits Base
  3: 
  4:     Public Overloads Overrides Sub Print()
  5:     End Sub
  6: End Class

By specifying both modifiers (Overloads Overrides) on the Print method you tell the VB compiler that the method Print overrides the base method but has to be considered as an overload to the rest of the Print methods defined on the base class.

Comments

  • Anonymous
    March 17, 2011
    Awesome.  Thank you!!