Does anyone know of a fast way in VB to go from a string to a generic type T
constrained to a valuetype (Of T as Structure
), when I know that T
will always be some number type?
This is too slow for my taste:
Return DirectCast(Convert.ChangeType(myStr, GetType(T)), T)
But it seems to be the only sane method of getting from a String
--> T
. I've tried using Reflector to see how Convert.ChangeType
works, and while I can convert from the String to a given number type via a hacked-up version of that code, I have no idea how to jam that type back into T
so it can be returned.
I'll add that part of the speed penalty I'm seeing (in a timing loop) is because the return value is getting assigned to a Nullable(Of T)
value. If I strongly-type my class for a specific number type (i.e., UInt16
), then I can vastly increase the performance, but then the class would need to be duplicated for each numeric type that I use.
It'd almost be nice if there was converter to/from T
while working on it in a generic method/class. Maybe there is and I'm oblivious to its existence?
Conclusion:
Testing the three provided implementations below and my original DirectCast/ChangeType form, @peenut's approach of using a prepared delegate to fetch the Parse
method from a basic type works. No error checking is done, however, so implementors need to remember to only use this with valuetypes that have a Parse
method available. Or extend the below to do error checking.
All runs were done on a 32bit system running Windows Server 2003 R2 with 4GB of RAM. Each "run" is 1,000,000 executions (ops) of the method to be tested, timed with StopWatch and reported back in milliseconds.
Original DirectCast(Convert.ChangeType(myStr, GetType(T)), T)
:
1000000 ops: 597ms
Average of 1000000 ops over 10 runs: 472ms
Average of 1000000 ops over 10 runs: 458ms
Average of 1000000 ops over 10 runs: 453ms
Average of 1000000 ops over 10 runs: 466ms
Average of 1000000 ops over 10 runs: 462ms
Using System.Reflection
and calling InvokeMethod
to get at the Parse
method:
1000000 ops: 12213ms
Average of 1000000 ops over 10 runs: 11468ms
Average of 1000000 ops over 10 runs: 11509ms
Average of 1000000 ops over 10 runs: 11524ms
Average of 1000000 ops over 10 runs: 11509ms
Average of 1000000 ops over 10 runs: 11490ms
Konrad's approach to generate IL code to access the Parse
method and store the call into a delegate:
1000000 ops: 352ms
Average of 1000000 ops over 10 runs: 316ms
Average of 1000000 ops over 10 runs: 315ms
Average of 1000000 ops over 10 runs: 314ms
Average of 1000000 ops over 10 runs: 314ms
Average of 1000000 ops over 10 runs: 314ms
peenut's approach of using a delegate to access the Parse
method directly:
1000000 ops: 272ms
Average of 1000000 ops over 10 runs: 272ms
Average of 1000000 ops over 10 runs: 275ms
Average of 1000000 ops over 10 runs: 274ms
Average of 1000000 ops over 10 runs: 272ms
Average of 1000000 ops over 10 runs: 273ms
Comparatively, peenut's approach is almost 200ms faster when executed 1,000,000 times in a tight loop, so his approach wins out. Although, Konrad's wasn't far behind and is itself a fascinating study of things like ILGenerator. Props to all who contributed!
Yes, I know about faster solution :-)
Faster solution is to use prepared delegate for given (generic) Type T. If you are only interested in String->(built-in numeric type), you can simply get Parse method with one argument (String).
Program to test speed of possibilities. Note that only first two methods are generic, 3rd and 4th methods are for comparison only.
Imports System.Reflection
Module Module1
Public Class Parser(Of T As Structure)
Delegate Function ParserFunction(ByVal value As String) As T
Public Shared ReadOnly Parse2 As ParserFunction = GetFunction()
Private Shared Function GetFunction() As ParserFunction
Dim t As Type = GetType(T)
Dim m As MethodInfo = t.GetMethod("Parse", New Type() {GetType(String)})
Dim d As ParserFunction = DirectCast( _
ParserFunction.CreateDelegate(GetType(ParserFunction), m), _
ParserFunction)
Return d
End Function
Public Shared Function Parse1(ByVal value As String) As T
Return DirectCast(Convert.ChangeType(value, GetType(T)), T)
End Function
End Class
Sub Main()
Dim w As New Stopwatch()
'test data:
Dim arrStr() As String = New String(12345678 - 1) {}
Dim r As New Random
For i As Integer = 0 To arrStr.Length - 1
arrStr(i) = r.Next().ToString()
Next
Dim arrInt1() As Integer = New Integer(arrStr.Length - 1) {}
Dim arrInt2() As Integer = New Integer(arrStr.Length - 1) {}
Console.WriteLine("1. method - Convert.ChangeType:")
w.Reset()
w.Start()
For i As Integer = 0 To arrStr.Length - 1
arrInt1(i) = Parser(Of Integer).Parse1(arrStr(i))
Next
w.Stop()
Console.WriteLine(w.Elapsed)
Console.WriteLine()
Console.WriteLine("2. method - prepared delegate:")
w.Reset()
w.Start()
For i As Integer = 0 To arrStr.Length - 1
arrInt2(i) = Parser(Of Integer).Parse2(arrStr(i))
Next
w.Stop()
Console.WriteLine(w.Elapsed)
Console.WriteLine()
Console.WriteLine("3. method - Integer.Parse:")
w.Reset()
w.Start()
For i As Integer = 0 To arrStr.Length - 1
arrInt2(i) = Integer.Parse(arrStr(i))
Next
w.Stop()
Console.WriteLine(w.Elapsed)
Console.WriteLine()
Console.WriteLine("4. method - CType:")
w.Reset()
w.Start()
For i As Integer = 0 To arrStr.Length - 1
arrInt2(i) = CType(arrStr(i), Integer)
Next
w.Stop()
Console.WriteLine(w.Elapsed)
Console.WriteLine()
End Sub
End Module
You can change number of tested elements, if you want. I used 12345678 random integers. Program outputs for me:
1. method - Convert.ChangeType: 00:00:03.5176071 2. method - prepared delegate: 00:00:02.9348792 3. method - Integer.Parse: 00:00:02.8427987 4. method - CType: 00:00:05.0542241
Ratio of times: 3.5176071 / 2.9348792 = 1.20
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