10

So I have the following code in C#:

public Container ConfigureSimpleInjector(IAppBuilder app)
{
    var container = new Container();

    container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();

    container.RegisterPackages();  

    app.Use(async (context, next) =>
    {
        using (AsyncScopedLifestyle.BeginScope(container))
        {
            await next();
        }
    });

    container.Verify();

    return container;
}

The app.Use() is defined as Owin.AppBuilderUserExtensions.Use() and looks like this:

public static IAppBuilder Use(this IAppBuilder app, Func<IOwinContext, Func<Task>, Task> handler);

The VB equivalent is as follows:

Public Function ConfigureSimpleInjector(app As IAppBuilder) As Container
    Dim container = New Container()

    container.Options.DefaultScopedLifestyle = New AsyncScopedLifestyle()

    container.RegisterPackages()

    app.Use(Async Sub(context, [next])
                Using AsyncScopedLifestyle.BeginScope(container)
                    Await [next]()
                End Using
            End Sub)

    container.Verify()

    Return container
End Function

For some reason, in the VB version, the app.Use() doesn't use the extension function that's available and simply uses IAppBuilder.Use() instead, as follows:

Function Use(middleware As Object, ParamArray args() As Object) As IAppBuilder

Why does the VB code not use the same extension function that C# does and how can I make it use that?

Edit

For clarity, the extension methods are not my own. They are from the Owin 3rd party library. So there is no option of renaming the extension methods.

Community
  • 1
  • 1
Jun Kang
  • 1,267
  • 1
  • 10
  • 27
  • 1
    Would it make a difference if you use `Func` instead of `Sub` in `app.Use(Async ...`? Currently the signature of the method is not matching the one from the framework. – keenthinker Jul 06 '17 at 20:32
  • I believe not, because as far as I'm aware, it's not supposed to return a value. I mean using `Function` instead throws an error, because then its expecting a return value. – Jun Kang Jul 06 '17 at 20:34
  • 2
    IMO it should. In order to use an overriden method, the signatures must match. Have a look at the [definition](https://msdn.microsoft.com/en-us/library/owin.appbuilderuseextensions.use(v=vs.113).aspx?cs-save-lang=1&cs-lang=vb#code-snippet-1). If not, how should the compiler know, that you want to call exactly this method? Currently the Sub it seen as a parameter (and thus the first method definition is used). How do you now (could you verify) in the C# version, that it calls the method with the Func parameter? – keenthinker Jul 06 '17 at 20:38
  • Sorry, I misinterpreted your code block as the extension method when I wrote the answer. If the Owin library is compiled into a DLL then I _believe_ the extension method should work in VB.NET as well, however I am not certain of it. – Visual Vincent Jul 06 '17 at 20:47

2 Answers2

5

In the C# code because next is Func and await returns a task, there is no return statement, but actualy a Task object is returned.

IMO the VB.NET code does not return a Task object, because the lambda is specified as Sub (= Action in C#).

Changing the Sub to Func in the VB.NET code should call the wanted method.

keenthinker
  • 7,645
  • 2
  • 35
  • 45
  • 1
    @JunKang : Try this then: `Async Function(context As IOwinContext, [next] As Func(Of Task)) As Task` – Visual Vincent Jul 06 '17 at 20:59
  • 1
    You could try instead using a lambda, to create a private function with the matching signature and pass it as a parameter. – keenthinker Jul 06 '17 at 21:03
  • The last suggestion I can think of... What about this very explicit way: `app.Use(New Func(Of IOwinContext, Func(Of Task), Task)(Async Function(context As IOwinContext, [next] As Func(Of Task)) As Task ...your code... ))`? – Visual Vincent Jul 06 '17 at 21:04
  • 1
    Thanks for the suggestions. Still doesn't work but I'll be working on this until it works so I guess I'll put up the answer if I ever find it. – Jun Kang Jul 06 '17 at 21:08
4

In VB (fiddle), you can pass a lambda expression when an object is required:

Public Shared Sub Main()
    MyMethod(Function() True)    ' compiles
End Sub

Public Shared Sub MyMethod(o As Object)
End Sub

In C# (fiddle), that doesn't work:

public static void Main()
{
    // Compilation error: Cannot convert lambda expression to type 'object' because it is not a delegate type.
    // You'd need to use: MyMethod(new Func<bool>(() => true));
    MyMethod(() => true);
}

public static void MyMethod(object o) { }

This is probably due to the fact that lambda expressions can be implicitly converted to delegates in VB but not in C#:

' Compiles
Dim d As System.Delegate = Function() True   
// Compilation error: Cannot convert lambda expression to type 'Delegate' because it is not a delegate type
System.Delegate d = () => true;

Thus, in your code example, VB finds a matching suitable instance method and uses it. In the C# code, no matching instance method is found (since your lambda expression does not match object), and the extension method is used instead.

Note that both languages prefer instance methods over extension methods if a matching method is found. This can lead to subtle bugs if a matching instance method is added later.


How to fix that? I suggest to use different names for the extension method and the instance method - other developers reading your code in the future will thank you for it. If that's not an option, you can always call the extension method explicitly:

Owin.AppBuilderUserExtensions.Use(app, Async Sub(context, [next]) ...)
Heinzi
  • 167,459
  • 57
  • 363
  • 519
  • 1
    Thanks for the correction to my (now-deleted) answer. I think you've got the right idea here. A lambda expression seems not to be implicitly convertible to `object` in C#. It might be worth adding that a `Func` *is* implicitly convertible, but of course a lambda expression is not a `Func`. In your example, If you replaced `MyMethod(() => true)` with `MyMethod(new Func(() => true))`, then it would compile, and it would prefer an instance-level `MyMethod` to an extension method even if the extension took a `Func` rather than an `object`. – Joe Farrell Jul 06 '17 at 21:35
  • @JoeFarrell: Exactly. I think it's due to the fact that lambdas can be implicitly converted to delegates in VB but not in C#. I have added a short example to my answer. – Heinzi Jul 06 '17 at 21:36
  • The extension methods are from the Owin third party library. So obviously renaming things is not an option. How would I call the extension method explicitly? I can't do `IAppBuilder.Use(...)` – Jun Kang Jul 06 '17 at 21:55
  • @JunKang: You're right, it should be `Owin.AppBuilderUserExtensions.Use(...)`. Should be fixed in my answer now. – Heinzi Jul 06 '17 at 22:01
  • @JunKang: It might be a good idea to file a bug report to the third-party library and tell them that their extension method cannot be used in VB (except by calling it explicitly) because its name conflicts with an instance method that accepts any number of object parameters and, thus, will always take precedence. – Heinzi Jul 06 '17 at 22:04