5

I have a C# library that has an extension method, something like:

public interface ISomething { ... }
public class SomethingA : ISomething { ... }
public class SomethingB : ISomething { ... }

public static class SomethingExtensions 
{
    public static int ExtensionMethod(this ISomething input, string extra) 
    {
    }
}

The extension works fine if called from C#, but has an issue if called from an external VB.Net application:

Dim something = Me.SomethingManager.GetSomething(key)
Dim result = something.ExtensionMethod("extra")

This compiles fine but throws an exception at run time:

Public member 'ExtensionMethod' on type 'SomethingB' not found.

If the VB.Net is changed to explicitly make the type the interface it works:

Dim something as ISomething = Me.SomethingManager.GetSomething(key)
Dim result = something.ExtensionMethod("extra")

Why? Why does the extension method work on the interface but not the class that implements it? Would I have the same issue if I used a subclass? Is VB.Net's implementation of extension methods incomplete?

Is there anything I can do in the C# library to make VB.Net work without the explicit interface?

Keith
  • 150,284
  • 78
  • 298
  • 434

4 Answers4

5

With Option Infer Off, this code...

Dim something = Me.SomethingManager.GetSomething(key)
Dim result = something.ExtensionMethod("extra")

...is the same as...

Dim something As Object = Me.SomethingManager.GetSomething(key)
Dim result As Object = something.ExtensionMethod("extra")

Since something is of type Object, it can't find the extension method, since it isn't defined on type Object.

Now, if you set Option Infer On, you will get the same results as with C#'s var keyword. Types will be automatically inferred. Note that this could also break existing code, but it can be enabled for a specific file, like Option Strict.

The best practice would be to set both Option Strict and Option Infer to On.

Meta-Knight
  • 17,626
  • 1
  • 48
  • 58
  • I don't think `Option Infer` is set either way, and I think it defaults to `On` - I'll have to test this. – Keith May 31 '12 at 16:40
  • Ahh, yes, if you don't specify an infer option Visual Studio defaults to on, but command line compilers default to off. `Option Infer On` consistently fixes this issue with loads less conversion pain than `Option Strict`. – Keith Jun 01 '12 at 08:12
  • 1
    @Keith: That conversion pain is probably a *good* thing in the long term. I would really encourage you to use Option Strict wherever possible. – Jon Skeet Jun 01 '12 at 08:17
  • @JonSkeet: I totally agree, but it's a painful option to retro-fit. `Option Strict On` is definitely best practice. `Option Infer On` fixed it for the VB.Net guys without them needing to change loads of code. – Keith Jun 01 '12 at 08:25
1

If it's throwing an exception rather than giving a compile-time error, that suggests you've got Option Strict off... I don't know what happens to extension methods in that case, as they're normally resolved at compile time, but with Option Strict off you've got late binding going on.

I suggest you turn Option Strict on, and all should be well...

(You'll also need to import the namespace, as per Richard's answer, but I'd assumed you'd already done that. You'll see a compile-time error if you forget to do that after turning option strict on, anyway.)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Unfortunately I don't have that option - they already have lots of VB.Net code that has `Option Strict Off`. They get loads of compile errors if they turn it on now. Does that mean that extension methods can't work with _any_ late-bound variables in VB.Net? – Keith May 31 '12 at 08:40
  • Yeah, turning `Option Strict On` just throws compile time exceptions when they don't have the `as ISomething` - what they want is to have the late binding and the extension method. They want `Dim` in VB to behave like `var` does in C#. I guess that just can't happen. – Keith May 31 '12 at 09:28
  • 2
    @Keith You can set `Option Strict On` on each individual code file. Just add the line `Option Strict On` to the top of the file, before anything else. You don't have to turn it on for the whole project. That CAN be very daunting when doing that in legacy code. – Steven Doggart May 31 '12 at 12:13
1

Cheers to Jon for pointing me in the right direction, but there's enough here to need a whole answer.

Extension methods are a compiler trick, so (in C#):

var something = this.SomethingManager.GetSomething(key);
var result = something.ExtensionMethod("extra");

Is converted at compile time to be:

ISomething something = this.SomethingManager.GetSomething(key);
int result = SomethingExtensions.ExtensionMethod(something, "extra");

The static extension method appearing as a method of the class is just compiler cleverness.

The problem is that Dim in VB is not the same as var in C#. Thank's to VB's late binding it's closer to dynamic.

So with the original example in VB:

Dim something = Me.SomethingManager.GetSomething(key)
Dim result = something.ExtensionMethod("extra")

Unlike C#, in VB figuring out the type of something is left until run time, and the compiler cleverness that makes the extension methods work doesn't happen. If the type is explicitly declared the extension method can be resolved, but if late-bound then extension methods just can't ever work.

If they use Option Strict On (like Jon suggests) then they're forced to always declare the type, early-binding happens and the compiler cleverness makes the extension methods work. It's best practice anyway, but as they haven't been doing so it would be a painful change for them to make.

The moral of this story: don't use extension methods in your API if you expect anyone from VB land near it :-S

Keith
  • 150,284
  • 78
  • 298
  • 434
0

Have you imported the namespace containing the class that defines the namespace.

Eg. you won't see any of the LINQ to Objects extension methods without a

Imports System.Linq
Richard
  • 106,783
  • 21
  • 203
  • 265
  • As I explained in the question, the method works if the interface is explicitly declared. The error is run-time, not compile time. – Keith May 31 '12 at 08:10