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:
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;
}
}
So what gives? Is there a way in VB to call a method dynamically on an argument to a function without using reflection?
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
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With