TL;DR version:
Use DirectCast()
instead of late binding or reflection for better runtime performance.
This question was very intriguing to me. I use DirectCast()
on a very regular basis for nearly every application that I've written. I've always used it because it makes IntelliSense work and if I don't with Option Strict On
then I'll get errors when compiling. Every once in a while I use Option Strict Off
if I'm in a hurry and I'm testing a design concept or if I'm in a hurry for a quick and dirty solution to a one-off problem. I've never given a second thought to performance effects in using it because I've never had to be concerned with runtime performance with the things I write.
Thinking about this question made me a little curious so I decided I'd test it out and see the differences for myself. I created a new Console application in Visual Studio and went to work. Below is the entire source code for the application. If you want to see the results for yourself you should be able to just copy/paste directly:
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
I tried running the application in Release
configuration as well as running the release config without the Visual Studio debugger attached. Here are the outputted results:
Release with Visual Studio Debugger:
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
Release without 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
Looking at those results it would look as though using DirectCast()
has almost no performance hit compared to the compiler adding in the casting. However when relying on the object to be late bound there is a HUGE performance hit and the execution slows down considerably. When using System.Reflection
, there is a slight slowdown over casting the object directly. I thought it was unusual that getting the PropertyInfo
was so much faster than using the .InvokeMember()
method.
Conclusion:
Whenever possible use DirectCast()
or cast the object directly. Using reflection should be reserved for only when you need it. Only use late bound items as a last resort. Truthfully though, if you're only modifying an object a few times here or there it's likely not going to matter in the grand scheme of things.
Instead you should be more concerned about how those methods could fail and how to keep them from doing so. For example, in the SetObjectInvokeMember()
method, if the sender
object didn't have the MyProperty
property, then that bit of code would have thrown an exception. In the SetObjectReflection()
method, the returned property info could have been nothing
which would have resulted in a NullReferenceException
.
DirectCast(obj, T)
is the same as C#'s(T)obj
. If that is correct, then it is probably the most efficient type cast (with the least hidden magic) that you can perform; followed byTryCast
(as
in C#). – DoorstopOption Strict On
will disable the ability to use late binding in most cases which would render this question moot. However, as the usual default for Visual Studio isOption Strict Off
(boo Microsoft), and the fact that late binding is useful at times (looking at you, LINQ andSystem.Reflection
), I would think that this question deserves an answer. – Taal