DirectCast具有性​​能和延迟/早期绑定有什么影响?

时间:2015-04-20 13:51:15

标签: vb.net performance directcast

我一直认为DirectCast()是相当便宜的,性能和内存方面的,并且它基本上看作是一种帮助我使用IntelliSense的方法,例如在事件处理程序中:

Public Sub myObject_EventHandler(sender As Object, e As System.EventArgs)
    'Explicit casting with DirectCast
    Dim myObject As myClass = DirectCast(sender, myClass)

    myObject.MyProperty = "myValue"
End Sub

我认为这对于我作为开发人员来说显然更好,但对于编译的代码和最终的性能也是如此,因为它启用了"早期绑定"而不是......

Public Sub myObject_EventHandler(sender As Object, e As System.EventArgs)
    'No casting at all (late binding)
    myObject.MyProperty = "myValue"
End Sub

...也编译和运行,但如果我正确地得到了条款,则使用"后期绑定"即假设sender实际上是myClass个对象。

关于性能,延迟/早期绑定或其他任何内容,上面的第一个片段和下面的片段之间有什么区别:

Public Sub myObject_EventHandler(sender As Object, e As System.EventArgs)
    'Implicit casting via variable declaration
    Dim myObject As myClass = sender

    myObject.MyProperty = "myValue"
End Sub

显式DirectCast()调用是有用/有害还是在编译器优化代码后没有区别?

2 个答案:

答案 0 :(得分:7)

TL; DR版本: 使用DirectCast()代替后期绑定或反射,以获得更好的运行时性能。

这个问题对我来说非常有趣。我经常使用DirectCast()来编写我编写的几乎所有应用程序。我一直使用它,因为它使IntelliSense工作,如果我不使用Option Strict On那么我在编译时会遇到错误。每隔一段时间我就会使用Option Strict Off如果我赶时间,我正在测试一个设计概念,或者我是否急于快速而肮脏地解决一次性问题。我从来没有再考虑过使用它时的性能影响,因为我从来不必关心我写的东西的运行时性能。

考虑这个问题让我有点好奇,所以我决定测试一下,看看自己的差异。我在Visual Studio中创建了一个新的控制台应用程序并开始工作。以下是该应用程序的完整源代码。如果您想亲自查看结果,您应该可以直接复制/粘贴:

Option Strict Off
Option Explicit On
Imports System.Diagnostics
Imports System.Reflection
Imports System.IO
Imports System.Text


Module Module1
    Const loopCntr As Int32 = 1000000
    Const iterationCntr As Int32 = 5
    Const resultPath As String = "C:\StackOverflow\DirectCastResults.txt"

    Sub Main()
        Dim objDirectCast As New MyObject("objDirectCast")
        Dim objImplicitCasting As New MyObject("objImplicitCasting")
        Dim objLateBound As New MyObject("objLateBound")
        Dim objReflection As New MyObject("objReflection")
        Dim objInvokeMember As New MyObject("objInvokeMember")
        Dim sbElapsed As New StringBuilder : sbElapsed.Append("Running ").Append(iterationCntr).Append(" iterations for ").Append(loopCntr).AppendLine(" loops.")
        Dim sbAverage As New StringBuilder : sbAverage.AppendLine()

        AddHandler objDirectCast.ValueSet, AddressOf SetObjectDirectCast
        AddHandler objImplicitCasting.ValueSet, AddressOf SetObjectImplictCasting
        AddHandler objLateBound.ValueSet, AddressOf SetObjectLateBound
        AddHandler objReflection.ValueSet, AddressOf SetObjectReflection
        AddHandler objInvokeMember.ValueSet, AddressOf SetObjectInvokeMember

        For Each myObj As MyObject In {objDirectCast, objImplicitCasting, objLateBound, objReflection, objInvokeMember}
            Dim resultlist As New List(Of TimeSpan)
            sbElapsed.AppendLine().Append("Time (seconds) elapsed for ").Append(myObj.Name).Append(" is: ")
            For i = 1 To iterationCntr
                Dim stpWatch As New Stopwatch
                stpWatch.Start()
                For _i = 0 To loopCntr
                    myObj.SetValue(_i)
                Next
                stpWatch.Stop()
                sbElapsed.Append(stpWatch.Elapsed.TotalSeconds.ToString()).Append(", ")
                resultlist.Add(stpWatch.Elapsed)
                Console.WriteLine(myObj.Name & " is done.")
            Next

            Dim totalTicks As Long = 0L
            resultlist.ForEach(Sub(x As TimeSpan) totalTicks += x.Ticks)
            Dim averageTimeSpan As New TimeSpan(totalTicks / CLng(resultlist.Count))
            sbAverage.Append("Average elapsed time for ").Append(myObj.Name).Append(" is: ").AppendLine(averageTimeSpan.ToString)
        Next

        Using strWriter As New StreamWriter(File.Open(resultPath, FileMode.Create, FileAccess.Write))
            strWriter.WriteLine(sbElapsed.ToString)
            strWriter.WriteLine(sbAverage.ToString)
        End Using
    End Sub

    Sub SetObjectDirectCast(sender As Object, newValue As Int32)
        Dim myObj As MyObject = DirectCast(sender, MyObject)
        myObj.MyProperty = newValue
    End Sub

    Sub SetObjectImplictCasting(sender As Object, newValue As Int32)
        Dim myObj As MyObject = sender
        myObj.MyProperty = newValue
    End Sub

    Sub SetObjectLateBound(sender As Object, newValue As Int32)
        sender.MyProperty = newValue
    End Sub

    Sub SetObjectReflection(sender As Object, newValue As Int32)
        Dim pi As PropertyInfo = sender.GetType().GetProperty("MyProperty", BindingFlags.Public + BindingFlags.Instance)
        pi.SetValue(sender, newValue, Nothing)
    End Sub

    Sub SetObjectInvokeMember(sender As Object, newValue As Int32)
        sender.GetType().InvokeMember("MyProperty", BindingFlags.Instance + BindingFlags.Public + BindingFlags.SetProperty, Type.DefaultBinder, sender, {newValue})
    End Sub
End Module

Public Class MyObject
    Private _MyProperty As Int32 = 0
    Public Event ValueSet(sender As Object, newValue As Int32)
    Public Property Name As String

    Public Property MyProperty As Int32
        Get
            Return _MyProperty
        End Get
        Set(value As Int32)
            _MyProperty = value
        End Set
    End Property

    Public Sub New(objName As String)
        Me.Name = objName
    End Sub

    Public Sub SetValue(newvalue As Int32)
        RaiseEvent ValueSet(Me, newvalue)
    End Sub

End Class

我尝试在Release配置中运行应用程序,并在没有附加Visual Studio调试器的情况下运行发布配置。以下是输出的结果:

使用Visual Studio调试器发布:

Running 5 iterations for 1000000 loops.

Time (seconds) elapsed for objDirectCast is: 0.0214367, 0.0155618, 0.015561, 0.015544, 0.015542, 
Time (seconds) elapsed for objImplicitCasting is: 0.014661, 0.0148947, 0.015051, 0.0149164, 0.0152732, 
Time (seconds) elapsed for objLateBound is: 4.2572548, 4.2073932, 4.3517058, 4.480232, 4.4216707, 
Time (seconds) elapsed for objReflection is: 0.3900658, 0.3833916, 0.3938861, 0.3875427, 0.4558457, 
Time (seconds) elapsed for objInvokeMember is: 1.523336, 1.1675438, 1.1519875, 1.1698862, 1.2878384, 

Average elapsed time for objDirectCast is: 00:00:00.0167291
Average elapsed time for objImplicitCasting is: 00:00:00.0149593
Average elapsed time for objLateBound is: 00:00:04.3436513
Average elapsed time for objReflection is: 00:00:00.4021464
Average elapsed time for objInvokeMember is: 00:00:01.2601184

在没有 Visual Studio Debugger的情况下发布

Running 5 iterations for 1000000 loops.

Time (seconds) elapsed for objDirectCast is: 0.0073776, 0.0055385, 0.0058196, 0.0059637, 0.0057557, 
Time (seconds) elapsed for objImplicitCasting is: 0.0060359, 0.0056653, 0.0065522, 0.0063639, 0.0057324, 
Time (seconds) elapsed for objLateBound is: 4.4858827, 4.1643164, 4.2380467, 4.1217441, 4.1270739, 
Time (seconds) elapsed for objReflection is: 0.3828591, 0.3790779, 0.3849563, 0.3852133, 0.3847144, 
Time (seconds) elapsed for objInvokeMember is: 1.0869766, 1.0808392, 1.0881596, 1.1139259, 1.0811786, 

Average elapsed time for objDirectCast is: 00:00:00.0060910
Average elapsed time for objImplicitCasting is: 00:00:00.0060699
Average elapsed time for objLateBound is: 00:00:04.2274128
Average elapsed time for objReflection is: 00:00:00.3833642
Average elapsed time for objInvokeMember is: 00:00:01.0902160

查看这些结果,看起来使用DirectCast()与添加到转换中的编译器相比几乎没有性能损失。但是,当依赖于对象后期绑定时,会出现巨大性能命中,执行速度会大大降低。使用System.Reflection时,直接投射对象会略微减速。我认为让PropertyInfo比使用.InvokeMember()方法快得多是不寻常的。

<强>结论: 尽可能使用DirectCast()或直接投射对象。只有在需要时才应保留使用反射。仅使用迟到的物品作为最后的手段。但实实在在,如果你只是在这里或那里修改一个物体几次,那么在宏观方案中它可能就不那么重要了。

相反,你应该更关心这些方法如何失败以及如何防止它们这样做。例如,在SetObjectInvokeMember()方法中,如果sender对象没有MyProperty属性,那么该位代码就会抛出异常。在SetObjectReflection()方法中,返回的属性信息可能是nothing,这可能会产生NullReferenceException

答案 1 :(得分:2)

我建议在for循环中运行后期绑定和直接强制转换大约100,000次,看看两者之间是否存在时间差。

为两个循环创建Stop监视并打印出结果。如果有任何不同,请告诉我们。 100,000次可能太低,你实际上可能会运行更长时间。