2011-01-06 56 views
8

有谁知道VB中的一种快捷方式,从字符串到T约束为一个值类型(Of T as Structure),当我知道T将永远是一些数字类型?当T是一个valuetype时,将字符串转换为泛型类型T的更快捷方式是什么?

这是对我的口味过于缓慢:

Return DirectCast(Convert.ChangeType(myStr, GetType(T)), T) 

但它似乎是从String获得的唯一理智的方法 - >T。我试过使用反射器来看看Convert.ChangeType是如何工作的,虽然我可以通过该代码的黑客版本从字符串转换为给定的数字类型,但我不知道如何将该类型重新插入T,以便它可以被退回。

我将添加我看到的速度惩罚的一部分(在一个时间循环中)是因为返回值被赋值为Nullable(Of T)值。如果我强烈地键入我的班级的特定数字类型(即UInt16),那么我可以大大提高性能,但是对于我使用的每种数字类型,该班级都需要重复。

如果在通用方法/类中使用/从T进行转换时,它几乎会很好。也许有,我忘了它的存在?


结论
测试以下三个提供的实现和我原来的DirectCast /一changeType形式,使用准备好的委托来从一个基本类型作品Parse方法@ peenut的做法。但是,没有进行错误检查,所以实现者需要记住只在具有可用的Parse方法的值类型中使用它。或者扩展下面的错误检查。

所有运行都是在运行带有4GB内存的Windows Server 2003 R2的32位系统上完成的。每个“运行”是要测试的方法的1,000,000个执行(ops),使用StopWatch进行计时并以毫秒为单位进行报告。

原始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 


使用System.Reflection,并呼吁InvokeMethod获得在Parse方法:

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 


康拉德的方法来生成IL代码来访问Parse方法和存储呼叫进入代表:

使用委托的


peenut的方式来访问Parse方法直接:

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 



相比之下,peenut的做法几乎是200毫秒更快紧密循环执行1,000,000次的时候,所以他的做法胜出。尽管康拉德并不遥远,并且本身就是一个像ILGenerator这样的迷人研究。道具所有谁贡献!

回答

9

是的,我知道:-)

更快的解决方案更快的解决方案是使用给定(通用)准备委托类型T.如果你只对字符串感兴趣 - >(内置数字式),你可以简单地用一个参数(String)获得Parse方法。

程序测试速度的可能性。请注意,只有前两种方法是通用的,第三种和第四种方法仅用于比较。

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 

如果需要,您可以更改测试元素的数量。我用12345678随机整数。我计划输出:

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

率的时间:3.5176071/2.9348792 = 1.20

0

可能不是一个答案,而是另一个问题。用这种方法你会取得什么成果?让我们想象一下,你在某种程度上实现这样的方法(对不起,C#在Vb.Net岗位,但希望你的想法):

T Convert<T>(string strInput) { ... } 

,你会使用这个方法只适用范围有限的类型:双,INT ,Int16的,等于是你会使用这样的:

double x = Convert<double>(myStr); 

我没有看到这样的方法任何好处,因为同样的原因而没有方法,你可以这样写:

double x = double.Parse(myStr); 

所以我在试着g要说的是,如果没有你的魔法,你会写相同数量的代码来使用它。我没有看到该方法的任何好处。我错过了一些用例吗?

+0

我想你错过了什么,我试图做的。假设一个泛型方法`Public Function Foo(Of T As Structure)(ByVal str As String)As T`。我必须返回类型`T`,大概是泛型类中的变量(`Dim tmp As T`)。如果在`Foo`中,我做了`Dim tmpDbl As Double = Double.Parse(str)`,那么当我尝试`返回tmpDbl`时,IDE会抛出一个错误,因为Double不能转换为`T`。因此,我的原始文章中列出的`DirectCast` /`Convert.ChangeType`组合很慢。 – Kumba 2011-01-13 03:02:11

+0

谢谢,这就是我错过的一点 - 这个泛型可以在其他泛型类中使用。 – Snowbear 2011-01-13 13:20:28

0

我没有VB编译器,所以我不能测试它,但下面的工程在C#中。我怀疑它的速度更快,虽然,因为它使用反射:

Public Shared Function Parse(Of T As Strcture)(ByVal value As String) As T 
    Dim type = GetType(T) 
    Dim result = type.InvokeMember(_ 
     "Parse", _ 
     BindingFlags.Public Or BindingFlags.Static Or BindingFlags.InvokeMethod, _ 
     Nothing, Nothing, new Object() { value }) 
    Return DirectCast(result, T) 
End Function 

的一种方式加快这是创建从T.Parse成员,而不是InvokeMember和缓存为每种类型的动态方法的动态方法。这意味着第一次调用的开销会更大(编译动态方法),但后续运行会更快。

+0

错误,你不需要创建新的功能,你已经有了你需要的一个 - 解析。看看我的解决方案;-) – peenut 2011-01-13 09:22:49

2

下面是使用前面提到的DynamicMethod的不同方法。

再说一遍,我无法测试VB代码(测试调用中的单声道编译器扼流圈,尽管不是代码本身),但我相信这是正确的。它的C#相当于作品和下面的代码是一个1:1的翻译:

public class Parser(of T as Structure) 
    delegate function ParserFunction(value as String) as T 
    private shared readonly m_parse as ParserFunction 

    shared sub new() 
     dim tt as Type = gettype(T) 
     dim argumentTypes as Type() = new Type() { gettype(String) } 
     dim typeDotParse as MethodInfo = tt.GetMethod("Parse", argumentTypes) 
     dim method as new DynamicMethod("Parse", tt, argumentTypes) 

     dim il as ILGenerator = method.GetILGenerator() 
     il.Emit(OpCodes.Ldarg_0) 
     il.Emit(OpCodes.Call, typeDotParse) 
     il.Emit(OpCodes.Ret) 

     m_parse = directcast(_ 
      method.CreateDelegate(gettype(ParserFunction)), _ 
      ParserFunction) 
    end sub 

    public shared function Parse(byval value As String) As T 
     return m_parse(value) 
    end function 
end class 

这个代码可以,如果你有最近的VB的版本被清除了。同样,单声道编译器还不知道Option Infer等。

这段代码的功能是为每个需要的代码编译一个单独的解析方法。此解析方法仅将实际解析委托给共享类型的方法(例如Integer.Parse)。一旦编译完成,此代码不需要任何额外的投射,不需要装箱,也不需要Nullable s。

的代码被称为如下:

Dim i As Integer = Parser(Of Integer).Parse("42") 

除了编译一次性开销,这种方法应该是最快的,因为没有其他开销:只是一个函数调用实际的解析常规。它不会比这更快。

+0

今天晚些时候我将不得不测试这个......我想你也给了我一个我还没有想过要问的问题的另一个答案 - 内联IL(从某种意义上说)。 – Kumba 2011-01-12 20:30:19

相关问题