RobV
RobV

Reputation: 28646

VB Compiler does not do implicit casts to Object?

I've recently had a strange issue with one of my APIs reported. Essentially for some reason when used with VB code the VB compiler does not do implicit casts to Object when trying to invoke the ToString() method.

The following is a minimal code example firstly in C# and secondly in VB:

    Graph g = new Graph();
    g.LoadFromEmbeddedResource("VDS.RDF.Configuration.configuration.ttl");

    foreach (Triple t in g.Triples)
    {
        Console.WriteLine(t.Subject.ToString());
    }

The above compiles and runs fine while the below does not:

    Dim g As Graph = New Graph()
    g.LoadFromEmbeddedResource("VDS.RDF.Configuration.configuration.ttl")

    For Each t As Triple In g.Triples
        Console.WriteLine(t.Subject.ToString())
    Next

The second VB example gives the following compiler exception:

Overload resolution failed because no accessible 'ToString' accepts this number of arguments.

This appears to be due to the fact that the type of the property t.Subject that I am trying to write to the console has explicitly defined ToString() methods which take parameters. The VB compiler appears to expect one of these to be used and does not seem to implicitly cast to Object and use the standard Object.ToString() method whereas the C# compiler does.

Is there any way around this e.g. a VB compiler option or is it best just to ensure that the type of the property (which is an interface in this example) explicitly defines an unparameterized ToString() method to ensure compatability with VB?

Edit

Here are the additional details requested by Lucian

  1. Graph is an implementation of an interface but that is actually irrelevant since it is the INode interface which is the type that t.Subject returns which is the issue.
    INode defines two overloads for ToString() both of which take parameters
  2. Yes it is a compile time error
  3. No I do not use hide-by-name, the API is all written in C# so I couldn't generate that kind of API if I wanted to

Note that I've since added an explicit unparameterized ToString() overload to the interface which has fixed the issue for VB users.

Upvotes: 2

Views: 353

Answers (2)

Lucian Wischik
Lucian Wischik

Reputation: 2336

RobV, I'm the VB spec lead, so I should be able to answer your question, but I'll need some clarification please...

  • What are the overloads defined on "Graph"? It'd help if you could make a self-contained repro. It's hard to explain overloading behavior without knowing the overload candidates :)

  • You said it failed with a "compiler exception". That doesn't really exist. Do you mean a "compile-time error"? Or a "run-time exception"?

  • Something to check is whether you're relying on any kind of "hide-by-name" vs "hide-by-sig" behavior. C# compiler only ever emits "hide-by-sig" APIs; VB compiler can emit either depending on whether you use the "Shadows" keyword.

  • C# overload algorithm is to walk up the inheritance hierarchy level by level until it finds a possible match; VB overload algorithm is to look at all levels of the inheritance hierarchy simultaneously to see which has the best match. This is all a bit theoretical, but with a small self-contained repro of your problem I could explain what it means in practice.

Hans, I don't think your explanation is the right one. Your code gives compile-time error "BC30455: Argument not specified for parameter 'mumble' of ToString". But RobV had experienced "Overload resolution failed because no accessible 'ToString' accepts this number of arguments".

Upvotes: 3

Hans Passant
Hans Passant

Reputation: 941277

Here's a repro of this behavior. It also shows you the workaround, cast with CObj():

Module Module1
    Sub Main()
        Dim itf As IFoo = New CFoo()
        Console.WriteLine(itf.ToString())        '' Error BC30455
        Console.WriteLine(CObj(itf).ToString())  '' Okay
    End Sub
End Module

Interface IFoo
    Function ToString(ByVal mumble As Integer) As String
End Interface

Class CFoo
    Implements IFoo
    Function ToString1(ByVal mumble As Integer) As String Implements IFoo.ToString
        Return "foo"
    End Function
End Class

I think this is annotated in the VB.NET Language Specification, chapter 11.8.1 "Overloaded method resolution":

The justification for this rule is that if a program is loosely-typed (that is, most or all variables are declared as Object), overload resolution can be difficult because all conversions from Object are narrowing. Rather than have the overload resolution fail in many situations (requiring strong typing of the arguments to the method call), resolution the appropriate overloaded method to call is deferred until run time. This allows the loosely-typed call to succeed without additional casts.

An unfortunate side-effect of this, however, is that performing the late-bound call requires casting the call target to Object. In the case of a structure value, this means that the value must be boxed to a temporary. If the method eventually called tries to change a field of the structure, this change will be lost once the method returns.

Interfaces are excluded from this special rule because late binding always resolves against the members of the runtime class or structure type, which may have different names than the members of the interfaces they implement.

Not sure. I'd transliterate it as: VB.NET is a loosely typed language where many object references are commonly late bound. This makes method overload resolution perilous.

Upvotes: 2

Related Questions