Frage Welchen Einfluss hat DirectCast auf eine Performance und eine späte / frühe Bindung?


Ich dachte immer DirectCast() war ziemlich preiswert, perforance- und memory-weise, und sah es im Grunde als eine Möglichkeit, mir mit IntelliSense, z. in Ereignishandlern:

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

Ich dachte, das war natürlich besser für mich als Entwickler, aber auch für den kompilierten Code und die daraus resultierende Performance, denn es ermöglichte "frühe Bindung" im Gegensatz zu ...

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

... das kompiliert und auch läuft, aber "späte Bindung" verwendet, wenn ich die Begriffe richtig verstanden habe. unter der Annahme, dass sender ist in der Tat ein myClass Objekt.

In Bezug auf die Leistung, die späte / frühe Bindung oder irgendetwas anderes, was sind die Unterschiede zwischen dem ersten und dem folgenden ersten Snippet?

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

Ist das explizit DirectCast() nennen Sie nützlich / schädlich oder macht es keinen Unterschied, nachdem der Compiler den Code optimiert hat?


10
2018-04-20 13:51


Ursprung


Antworten:


TL; DR-Version: Benutzen DirectCast() statt späte Bindung oder Reflektion für bessere Laufzeitleistung.

Diese Frage war sehr faszinierend für mich. ich benutze DirectCast() sehr regelmäßig für fast jede Anwendung, die ich geschrieben habe. Ich habe es immer benutzt, weil es IntelliSense funktioniert und wenn ich nicht mit Option Strict On dann bekomme ich Fehler beim Kompilieren. Hin und wieder benutze ich Option Strict Off wenn ich es eilig habe und ich ein Designkonzept teste oder wenn ich es eilig habe für eine schnelle und dreckige Lösung für ein einmaliges Problem. Ich habe nie über Leistungseffekte bei der Verwendung nachgedacht, weil ich mich nie mit der Laufzeitleistung mit den Dingen beschäftigen musste, die ich schreibe.

Nachdenken über diese Frage machte mich ein bisschen neugierig, also entschied ich, dass ich es ausprobieren und die Unterschiede für mich selbst sehen würde. Ich habe eine neue Konsolenanwendung in Visual Studio erstellt und mit der Arbeit begonnen. Im Folgenden finden Sie den gesamten Quellcode für die Anwendung. Wenn Sie die Ergebnisse für sich selbst sehen möchten, sollten Sie in der Lage sein, direkt zu kopieren / einfügen:

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

Ich habe versucht, die Anwendung zu starten Release Konfiguration sowie das Ausführen der Release-Konfiguration ohne den Visual Studio-Debugger angeschlossen. Hier sind die ausgegebenen Ergebnisse:

Release mit 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

Freisetzung ohne 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

Wenn man diese Ergebnisse betrachtet, sieht es so aus, als ob man es benutzt DirectCast() hat im Vergleich zum Compiler, der das Casting hinzufügt, fast keine Leistungseinbußen. Wenn man sich jedoch darauf verlässt, dass das Objekt zu spät gebunden ist, gibt es a ENORM Performance-Hit und die Ausführung verlangsamt sich erheblich. Beim Benutzen System.Reflection, gibt es eine leichte Verlangsamung über das Gießen des Objekts direkt. Ich dachte, es wäre ungewöhnlich, dass man das bekommt PropertyInfo war so viel schneller als die Verwendung der .InvokeMember() Methode.

Fazit: Wann immer es möglich ist DirectCast() oder das Objekt direkt werfen. Die Verwendung von Reflektionen sollte nur dann reserviert werden, wenn Sie sie benötigen. Verwenden Sie nur spät gebundene Elemente als letzte Möglichkeit. Ehrlich gesagt, wenn Sie ein Objekt nur ein paar Mal hier oder dort modifizieren, wird es wahrscheinlich im großen Ganzen nicht von Bedeutung sein.

Stattdessen sollten Sie sich mehr Gedanken darüber machen, wie diese Methoden scheitern könnten und wie sie davon abgehalten werden können. Zum Beispiel in der SetObjectInvokeMember() Methode, wenn die sender Objekt hatte nicht die MyProperty Eigenschaft, dann hätte dieses Bit Code eine Ausnahme ausgelöst. In dem SetObjectReflection() Methode könnte die zurückgegebene Eigenschaft info gewesen sein nothing was hätte ergeben, a NullReferenceException.


7
2018-04-20 21:15



Ich würde vorschlagen, die späte Bindung und die direkte Besetzung in einer for-Schleife etwa 100.000 Mal auszuführen und zu sehen, ob es einen Zeitunterschied zwischen den beiden gibt.

Create Stop wacht über beide Loops und druckt die Ergebnisse aus. Lassen Sie uns wissen, ob es einen Unterschied gibt. 100.000 Mal ist vielleicht zu niedrig und Sie können es tatsächlich länger laufen lassen.


2
2018-04-20 15:00