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?

asked Oct 6, 2015 at 8:11
\$\endgroup\$

1 Answer 1

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
answered Oct 6, 2015 at 9:50
\$\endgroup\$
9
  • \$\begingroup\$ doesn't DynamicInvoke perform utterly bad as compared to Invoke? \$\endgroup\$ Commented Oct 6, 2015 at 10:17
  • \$\begingroup\$ Sure, but if performance is an issue then, as always, generic is not the way to go. \$\endgroup\$ Commented Oct 6, 2015 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\$ Commented Oct 6, 2015 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\$ Commented Oct 6, 2015 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\$ Commented Oct 6, 2015 at 15:47

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.