5
\$\begingroup\$

I have a class which takes the method address and arguments, and executes it later when told to do so.

' need to turn option strict off due to Execute method executing late-bound code
Option Strict Off

Public Class WorkItem
    Private Action As Object
    Private Args() As Object

    Public Overloads Sub [Set](action As Action)
        SetArgs(action)
    End Sub

    Public Overloads Sub [Set](Of T)(action As Action(Of T), arg As T)
        SetArgs(action, arg)
    End Sub

    Public Overloads Sub [Set](Of T1, T2)(action As Action(Of T1, T2), arg1 As T1, arg2 As T2)
        SetArgs(action, arg1, arg2)
    End Sub

    '*** more overloads of [Set] method go here... 

    Private Sub SetArgs(ByVal action As Object, ParamArray args() As Object)
        Me.Action = action
        Me.Args = args
    End Sub

    Public Sub Execute()
        '-- early binding doesn't work
        'DirectCast(Me.Action, Action(Of T)).Invoke(Args(0))  

        '-- this works, but forces me to to keep option strict off
        Select Case Args.Length
            Case 0 : Me.Action.Invoke()
            Case 1 : Me.Action.Invoke(Args(0))
            Case 2 : Me.Action.Invoke(Args(0), Args(1))
            Case 3 : Me.Action.Invoke(Args(0), Args(1), Args(2))
            Case 4 : Me.Action.Invoke(Args(0), Args(1), Args(2), Args(3))
        End Select
    End Sub
End Class

Here is some tester code:

Public Class Form1
    Dim TheTask As WorkItem

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        TheTask = New WorkItem
        TheTask.Set(AddressOf DummyProc, TextBox1)
    End Sub

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        TheTask.Execute()
    End Sub

    Private Sub DummyProc(arg As TextBox)
        Threading.Thread.Sleep(1000)
        Debug.Print("work completed")
    End Sub
End Class

All this works with OPTION STRICT OFF

The WorkItem class obviously doesn't work with OPTION STRICT ON, due to the late-bound call in Execute method.

Is there any way I can convert the late-bound call to early binding?

| improve this question | | | | |
\$\endgroup\$
5
\$\begingroup\$
  • You're working with delegates so the Action variable should be declared as Delegate. Please note that you need to enclose the type in square brackets as Delegate is a reserved word.

  • Don't use Dim in class-scoped variables, instead apply the access level.

  • Declare your arrays in a .net fashion. The type should be Object().

  • All non-public fields should be in lowerCamelCase.

  • There's no need to apply the Overloads modifier as you're not redeclaring any existing methods.

Apply all the fixes allows you to perform a dynamic invoke.

Public Class WorkItem

    Private action As [Delegate]
    Private args As Object()

    Public Sub [Set](action As Action)
        Me.SetArgs(action)
    End Sub

    Public Sub [Set](Of T)(action As Action(Of T), arg As T)
        Me.SetArgs(action, arg)
    End Sub

    Public Sub [Set](Of T1, T2)(action As Action(Of T1, T2), arg1 As T1, arg2 As T2)
        Me.SetArgs(action, arg1, arg2)
    End Sub

    Private Sub SetArgs(ByVal action As [Delegate], ParamArray args As Object())
        Me.action = action
        Me.args = args
    End Sub

    Public Sub Execute()
        Me.action.DynamicInvoke(Me.args)
    End Sub

End Class

Alternative solution

Another approach would be to wrap the delegate in a parameterless action.

Public Class WorkItem

    Private method As Action

    Public Sub [Set](Of T)(action As Action(Of T), arg As T)
        Me.method = New Action(Sub() action.Invoke(arg))
    End Sub

    Public Sub [Set](Of T1, T2)(action As Action(Of T1, T2), arg1 As T1, arg2 As T2)
        Me.method = New Action(Sub() action.Invoke(arg1, arg2))
    End Sub

    Public Sub [Set](Of T1, T2, T3)(action As Action(Of T1, T2, T3), arg1 As T1, arg2 As T2, arg3 As T3)
        Me.method = New Action(Sub() action.Invoke(arg1, arg2, arg3))
    End Sub

    Public Sub Execute()
        Me.method.Invoke()
    End Sub

End Class

For old compilers

Public Delegate Sub Action()
Public Delegate Sub Action(Of T)(ByVal obj As T)
Public Delegate Sub Action(Of T1, T2)(ByVal arg1 As T1, ByVal arg2 As T2)
Public Delegate Sub Action(Of T1, T2, T3)(ByVal arg1 As T1, ByVal arg2 As T2, ByVal arg3 As T3)

Public Class WorkItem

    Private Class Closure(Of T)

        Public action As Action(Of T)
        Public arg As T

        Public Sub Invoke()
            Me.action.Invoke(Me.arg)
        End Sub

    End Class

    Private Class Closure(Of T1, T2)

        Public action As Action(Of T1, T2)
        Public arg1 As T1
        Public arg2 As T2

        Public Sub Invoke()
            Me.action.Invoke(Me.arg1, Me.arg2)
        End Sub

    End Class

    Private Class Closure(Of T1, T2, T3)

        Public action As Action(Of T1, T2, T3)
        Public arg1 As T1
        Public arg2 As T2
        Public arg3 As T3

        Public Sub Invoke()
            Me.action.Invoke(Me.arg1, Me.arg2, Me.arg3)
        End Sub

    End Class

    Private method As Action

    Public Sub [Set](Of T)(ByVal action As Action(Of T), ByVal arg As T)
        Dim closure As New Closure(Of T)
        closure.action = action
        closure.arg = arg
        Me.method = New Action(AddressOf closure.Invoke)
    End Sub

    Public Sub [Set](Of T1, T2)(ByVal action As Action(Of T1, T2), ByVal arg1 As T1, ByVal arg2 As T2)
        Dim closure As New Closure(Of T1, T2)
        closure.action = action
        closure.arg1 = arg1
        closure.arg2 = arg2
        Me.method = New Action(AddressOf closure.Invoke)
    End Sub

    Public Sub [Set](Of T1, T2, T3)(ByVal action As Action(Of T1, T2, T3), ByVal arg1 As T1, ByVal arg2 As T2, ByVal arg3 As T3)
        Dim closure As New Closure(Of T1, T2, T3)
        closure.action = action
        closure.arg1 = arg1
        closure.arg2 = arg2
        closure.arg3 = arg3
        Me.method = New Action(AddressOf closure.Invoke)
    End Sub

    Public Sub Execute()
        Me.method.Invoke()
    End Sub

End Class
| improve this answer | | | | |
\$\endgroup\$
  • \$\begingroup\$ doesn't DynamicInvoke perform utterly bad as compared to Invoke? \$\endgroup\$ – Pradeep Kumar Oct 6 '15 at 10:17
  • \$\begingroup\$ Sure, but if performance is an issue then, as always, generic is not the way to go. \$\endgroup\$ – Bjørn-Roger Kringsjå Oct 6 '15 at 10:28
  • \$\begingroup\$ I cannot say if it's faster (benchmarking needed), but you could try to invoke by using the method info: Me.action.Method.Invoke(Me.action.Target, Me.args) \$\endgroup\$ – Bjørn-Roger Kringsjå Oct 6 '15 at 10:38
  • 2
    \$\begingroup\$ I doubt DynamicInvoke performs any worse than late-binding. (Again, benchmarking needed) Then again, I wouldn't be surprised if the late-binding was actually performed using DynamicInvoke by the compiler. \$\endgroup\$ – RubberDuck Oct 6 '15 at 11:10
  • \$\begingroup\$ The alternative solution is good. But unfortunately I'm stuck with .NET 3.5, which doesn't support inline Sub(). \$\endgroup\$ – Pradeep Kumar Oct 6 '15 at 15:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.