What effect does DirectCast have an performance and late/early binding?
Asked Answered
A

2

10

I always thought DirectCast() was fairly inexpensive, perforance- and memory-wise, and saw it basically as a way of helping me with IntelliSense, e.g. in event handlers:

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

I figured this was better, obviously, for me as a developer, but also for the compiled code and resulting performance, because it enabled "early binding" as opposed to ...

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

... which compiles and runs also, but uses "late binding", if I got the terms correctly. i.e. assuming that sender is in fact a myClass object.

In regards to performance, late/early binding, or anything else, what are the differences between the very first snippet above and the following one :

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

Is the explicit DirectCast() call useful/harmful or does it make no difference after the compiler has optimized the code?

Ageless answered 20/4, 2015 at 13:51 Comment(5)
Not related to the question but I would highly suggest you enable Option Strict on all of your projects.Nissa
If you know C#, then, according to this answer, 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 by TryCast (as in C#).Doorstop
@Nissa Turning Option 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 is Option Strict Off (boo Microsoft), and the fact that late binding is useful at times (looking at you, LINQ and System.Reflection ), I would think that this question deserves an answer.Taal
The 2nd snippet is quite expensive, it must resolve the property from its name and that requires Reflection. There is no difference between the 1st and 3rd. It auto-generates a DirectCast in the 3rd snippet since it knows the type of myObject. You'll want to play with the ildasm.exe utility to see these things for yourself. And use the Stopwatch class to measure these things for yourself.Threadfin
@HansPassant - As far as I'm concerned, you are proven correct yet again.Taal
L
8

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.

Loosejointed answered 20/4, 2015 at 21:15 Comment(3)
Late binding needs to use reflection to find the method to call, while the two casts are determined at compile-type and only need to perform a type-check to see if the cast can be done.Desecrate
@Desecrate I added two reflection methods into the fray, however neither are anywhere close to the time it takes for the late bound method.Taal
Summarising your results (which I've repeated), reflection is over an order of magnitude slower than casting, late-bound (and CallByName) is another order of magnitude slower again, and InvokeMember is somewhere between the two. I also found it is the SetValue call that is the slow part of the Reflection code (specifically caching the PropertyInfo only saves 22% time) and, as Hans Passant suggested, LinqPad shows the IL of SetObjectDirectCast and SetObjectImplictCasting are identical.Kowtko
O
2

I would suggest running the late binding and the direct cast in a for loop about 100,000 times and see if there is a time difference between the two.

Create Stop watches for both loops and print out the results. Let us know if there is any difference. 100,000 times may be too low and you may actually have it run longer.

Omeromero answered 20/4, 2015 at 15:0 Comment(4)
correct diagnostic here. doing it once is not a good test because of the JIT like Brandon B is showing in his code.Jurywoman
@Ahmedilyas I'm confused. Are you saying that mine is a good test because I tried without the JIT or are you saying that my test is bad because of the JIT?Taal
Brandon B he is saying that you are taking the performance hit of JIT the very first time it runs. So it is very likely that if you run the same code snippet multiple times, only the very first run would take 8 seconds.. you would have to average it out after multiple runs. If your program only does it once then closes then yes there is a huge problem. If it is a service that stays up for 6 months and runs 3000 times a day then you have not calculated the average.Omeromero
@Ahmedilyas I updated my answer to perform multiple iterations and calculate the average for each type. Didn't change the end results much though.Taal

© 2022 - 2024 — McMap. All rights reserved.