DirectCast 对性能和 late/early 绑定有什么影响?
What effect does DirectCast have an performance and late/early binding?
我一直认为 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
我认为这显然对作为开发人员的我来说更好,而且对编译代码和由此产生的性能也更好,因为它启用了 "early binding" 而不是 ...
Public Sub myObject_EventHandler(sender As Object, e As System.EventArgs)
'No casting at all (late binding)
myObject.MyProperty = "myValue"
End Sub
... 也可以编译和运行,但如果我的术语正确,则使用 "late binding"。即假设 sender
实际上是一个 myClass
对象。
关于性能、late/early 绑定或其他任何方面,上面的第一个代码段与以下代码段之间有什么区别:
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()
调用useful/harmful还是编译器优化代码后没有区别?
我建议 运行在 for 循环中将后期绑定和直接强制转换大约 100,000 次,看看两者之间是否存在时间差异。
为两个循环创建秒表并打印结果。让我们知道是否有任何区别。 100,000 次可能太低了,您实际上可能 运行 更长。
TL;DR 版本:
使用 DirectCast()
而不是后期绑定或反射以获得更好的运行时性能。
这个问题让我很感兴趣。对于我编写的几乎每个应用程序,我都定期使用 DirectCast()
。我一直使用它,因为它使 IntelliSense 工作,如果我不使用 Option Strict On
,那么我在编译时会出错。每隔一段时间,如果我赶时间并且正在测试设计概念,或者如果我急于为一次性问题寻找快速而肮脏的解决方案,我就会使用 Option Strict Off
。在使用它时,我从来没有考虑过性能影响,因为我从来不需要关心我写的东西的运行时性能。
想到这个问题让我有点好奇,所以我决定自己测试一下,看看有什么不同。我在 Visual Studio 中创建了一个新的控制台应用程序并开始工作。下面是应用程序的完整源代码。如果您想自己查看结果,您应该可以直接 copy/paste:
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:\Whosebug\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调试器:
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()
几乎没有性能损失。然而,当依赖于延迟绑定的对象时,性能会受到 HUGE 的影响,并且执行速度会大大降低。使用 System.Reflection
时,与直接投射对象相比,速度略有下降。我认为获取 PropertyInfo
比使用 .InvokeMember()
方法快得多是不寻常的。
结论:
尽可能使用 DirectCast()
或直接转换对象。应该只在需要时才使用反射。仅在万不得已时才使用后期绑定项目。但说实话,如果您只是在这里或那里修改一个对象几次,那么在宏伟的计划中它可能并不重要。
相反,您应该更关心这些方法如何失败以及如何防止它们失败。例如,在 SetObjectInvokeMember()
方法中,如果 sender
对象没有 MyProperty
属性,那么这段代码就会抛出异常。在 SetObjectReflection()
方法中,返回的 属性 信息可能是 nothing
,这会导致 NullReferenceException
。
我一直认为 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
我认为这显然对作为开发人员的我来说更好,而且对编译代码和由此产生的性能也更好,因为它启用了 "early binding" 而不是 ...
Public Sub myObject_EventHandler(sender As Object, e As System.EventArgs)
'No casting at all (late binding)
myObject.MyProperty = "myValue"
End Sub
... 也可以编译和运行,但如果我的术语正确,则使用 "late binding"。即假设 sender
实际上是一个 myClass
对象。
关于性能、late/early 绑定或其他任何方面,上面的第一个代码段与以下代码段之间有什么区别:
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()
调用useful/harmful还是编译器优化代码后没有区别?
我建议 运行在 for 循环中将后期绑定和直接强制转换大约 100,000 次,看看两者之间是否存在时间差异。
为两个循环创建秒表并打印结果。让我们知道是否有任何区别。 100,000 次可能太低了,您实际上可能 运行 更长。
TL;DR 版本:
使用 DirectCast()
而不是后期绑定或反射以获得更好的运行时性能。
这个问题让我很感兴趣。对于我编写的几乎每个应用程序,我都定期使用 DirectCast()
。我一直使用它,因为它使 IntelliSense 工作,如果我不使用 Option Strict On
,那么我在编译时会出错。每隔一段时间,如果我赶时间并且正在测试设计概念,或者如果我急于为一次性问题寻找快速而肮脏的解决方案,我就会使用 Option Strict Off
。在使用它时,我从来没有考虑过性能影响,因为我从来不需要关心我写的东西的运行时性能。
想到这个问题让我有点好奇,所以我决定自己测试一下,看看有什么不同。我在 Visual Studio 中创建了一个新的控制台应用程序并开始工作。下面是应用程序的完整源代码。如果您想自己查看结果,您应该可以直接 copy/paste:
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:\Whosebug\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调试器:
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()
几乎没有性能损失。然而,当依赖于延迟绑定的对象时,性能会受到 HUGE 的影响,并且执行速度会大大降低。使用 System.Reflection
时,与直接投射对象相比,速度略有下降。我认为获取 PropertyInfo
比使用 .InvokeMember()
方法快得多是不寻常的。
结论:
尽可能使用 DirectCast()
或直接转换对象。应该只在需要时才使用反射。仅在万不得已时才使用后期绑定项目。但说实话,如果您只是在这里或那里修改一个对象几次,那么在宏伟的计划中它可能并不重要。
相反,您应该更关心这些方法如何失败以及如何防止它们失败。例如,在 SetObjectInvokeMember()
方法中,如果 sender
对象没有 MyProperty
属性,那么这段代码就会抛出异常。在 SetObjectReflection()
方法中,返回的 属性 信息可能是 nothing
,这会导致 NullReferenceException
。