Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic method calling in VB without reflection

Tags:

dynamic

vb.net

I want to format any numeric type using a method call like so:

Option Infer On
Option Strict Off
Imports System.Runtime.CompilerServices

Namespace GPR
    Module GPRExtensions
        <Extension()>
        Public Function ToGPRFormattedString(value) As String
            ' Use VB's dynamic dispatch to assume that value is numeric
            Dim d As Double = CDbl(value)
            Dim s = d.ToString("N3")
            Dim dynamicValue = value.ToString("N3")
            Return dynamicValue
        End Function
    End Module
End Namespace

Now, from various discussions around the web (VB.Net equivalent for C# 'dynamic' with Option Strict On, Dynamic Keyword equivalent in VB.Net?), I would think that this code would work when passed a numeric type (double, Decimal, int, etc). It doesn't, as you can see in the screenshot:

Exception showing dynamic method call failing

I can explicitly convert the argument to a double and then .ToString("N3") works, but just calling it on the supposedly-dynamic value argument fails.

However, I can do it in C# with the following code (using LINQPad). (Note, the compiler won't let you use a dynamic parameter in an extension method, so maybe that is part of the problem.)

void Main()
{
    Console.WriteLine (1.ToGPRFormattedString());
}

internal static class GPRExtensions
{
    public static string ToGPRFormattedString(this object o)
    {
        // Use VB's dynamic dispatch to assume that value is numeric
        var value = o as dynamic;
        double d = Convert.ToDouble(value);
        var s = d.ToString("N3").Dump("double tostring");
        var dynamicValue = value.ToString("N3");
        return dynamicValue;
    }
}

Dynamic method invocation in C# works

So what gives? Is there a way in VB to call a method dynamically on an argument to a function without using reflection?

like image 476
Pat Avatar asked Jul 23 '13 20:07

Pat


1 Answers

To explicitly answer "Is there a way in VB to call a method dynamically on an argument to a function without using reflection?":

EDIT: I've now reviewed some IL disassembly (via LinqPad) and compared it to the code of CallByName (via Reflector) and using CallByName uses the same amount of Reflection as normal, Option Strict Off late binding.

So, the complete answer is: You can do this with Option Strict Off for all Object references, except where the method you're trying exists on Object itself, where you can use CallByName to get the same effect (and, in fact, that doesn't need Option Strict Off).

Dim dynamicValue = CallByName(value, "ToString", CallType.Method, "N3")

NB This is not actually the equivalent to the late binding call, which must cater for the possibility that the "method" is actually a(n indexed) property, so it actually calls the equivalent of:

Dim dynamicValue = CallByName(value, "ToString", CallType.Get, "N3")

for other methods, like Double.CompareTo.


Details

Your problem here is that Object.ToString() exists and so your code is not attempting any dynamic dispatch, but rather an array index lookup on the default String.Chars property of the String result from that value.ToString() call.

You can confirm this is what is happening at compile time by trying value.ToString(1,2), which you would prefer to attempt a runtime lookup for a two parameter ToString, but in fact fails with

Too many arguments to 'Public ReadOnly Default Property Chars(index As Integer) As Char'

at compile time.

You can similarly confirm all other non-Shared Object methods are called directly with callvirt, relying upon Overrides where available, not dynamic dispatch with calls to the Microsoft.VisualBasic.CompilerServices.NewLateBinding namespace, if you review the compiled code in IL.

like image 70
Mark Hurd Avatar answered Oct 12 '22 22:10

Mark Hurd