Passing a subroutine as a parameter to another subroutine

  • Thread starter Thread starter Guest
  • Start date Start date
G

Guest

Hi. In c/C++ i can pass the address of a subroutine to another subroutine as an actual parameter
How do I do that in VB .NET
What should be the syntax for a parameter to receive the address of a subroutine
Let's say theres a sub that creates buttons and I want it to receive as a parameter the address of the sub that handles the OnClick event for the button being created
How do I pass such a parameter

Thanks in advance

Richar
 
Richard,
You pass it as a Delegate using the AddressOf operator.

Something like:

Public Delegate Sub MyHandler(ByVal arg1 As String, ByVal arg2 As
Integer)

' Sub that accepts a routine as a parameter and calls that routine.
Public Sub MySub(ByVal handler As MyHandler, ByVal arg1 As String, ByVal
arg2 As Integer)
handler(arg1, arg2)
End Sub

' Sub that accepts a routine as a parameter and uses it to handle an
event.
Public Function CreateButton(ByVal handler As EventHandler, ByVal text
As String) As Button
Dim button As New Button
button.Text = text
AddHandler button.Click, handler
Return button
End Function

Private Sub MySampleSub(ByVal arg1 As String, ByVal arg2 As Integer)

End Sub

Private Sub Button_Click(ByVal sender As Object, ByVal e As EventArgs)

End Sub

Public Sub Sample()
MySub(AddressOf MySampleSub, "a", 1)
MySub(AddressOf MySampleSub, "b", 2)

Dim btn As Button
btn = CreateButton(AddressOf Button_Click, "1")
btn = CreateButton(AddressOf Button_Click, "2")
btn = CreateButton(AddressOf Button_Click, "3")
End Sub

Hope this helps
Jay

Richard Grant said:
Hi. In c/C++ i can pass the address of a subroutine to another subroutine as an actual parameter.
How do I do that in VB .NET?
What should be the syntax for a parameter to receive the address of a subroutine?
Let's say theres a sub that creates buttons and I want it to receive as a
parameter the address of the sub that handles the OnClick event for the
button being created.
 
Wow... Well explained Jay.

=)
Jay B. Harlow said:
Richard,
You pass it as a Delegate using the AddressOf operator.

Something like:

Public Delegate Sub MyHandler(ByVal arg1 As String, ByVal arg2 As
Integer)

' Sub that accepts a routine as a parameter and calls that routine.
Public Sub MySub(ByVal handler As MyHandler, ByVal arg1 As String, ByVal
arg2 As Integer)
handler(arg1, arg2)
End Sub

' Sub that accepts a routine as a parameter and uses it to handle an
event.
Public Function CreateButton(ByVal handler As EventHandler, ByVal text
As String) As Button
Dim button As New Button
button.Text = text
AddHandler button.Click, handler
Return button
End Function

Private Sub MySampleSub(ByVal arg1 As String, ByVal arg2 As Integer)

End Sub

Private Sub Button_Click(ByVal sender As Object, ByVal e As EventArgs)

End Sub

Public Sub Sample()
MySub(AddressOf MySampleSub, "a", 1)
MySub(AddressOf MySampleSub, "b", 2)

Dim btn As Button
btn = CreateButton(AddressOf Button_Click, "1")
btn = CreateButton(AddressOf Button_Click, "2")
btn = CreateButton(AddressOf Button_Click, "3")
End Sub

Hope this helps
Jay

subroutine
as an actual parameter. a
parameter the address of the sub that handles the OnClick event for the
button being created.
 
I agree, nice explanation. I have a situation where I want to use
delegates as well:

I have an application in which I want an "action queue." I would like to
make that queue as generic as possible.

Public Delegate Sub AnyRoutine( ___________ )

Then to add to the queue, ActionQueue.Enqueue(DelaySeconds,AddressOf Sub1,
Parm1, Parm2) and also ActionQueue.Enqueue(DelaySeconds,AddressOf Sub2,
parm_1, parm_2, parm_3). That is, the passed routines may have different
parameter lists. The DelaySeconds is the wait required before executing
the passed routine.

Do I put ParmArray as the parameter in the delegate?

Currently I have one parameter in the delegate and put the parms required
in a structure. The shape of this structure depends on the particular
routine. Thus all subroutines that can be in my ActionQueue have the same
signature, a single parm. Is this the best/only way?
 
Ot,
Then to add to the queue, ActionQueue.Enqueue(DelaySeconds,AddressOf Sub1,
Parm1, Parm2) and also ActionQueue.Enqueue(DelaySeconds,AddressOf Sub2,
parm_1, parm_2, parm_3). That is, the passed routines may have different
parameter lists. The DelaySeconds is the wait required before executing
the passed routine.
Its unclear by your example. Are the parameters set when you enqueue the
item or when you dequeue the item?


3 choices that I would consider include, but are not limited to:

1) Define 2 Delegates one for 2 parameters & 1 for 3 parameters, then use
the TypeOf Is operator to decide which delegate to invoke.

Public Delegate Sub AnyRoutine1(ByVal parm1 As String, ByVal parm2 As
Integer)

Public Delegate Sub AnyRoutine2(ByVal parm1 As String, ByVal parm2 As
Integer, ByVal parm3 As Double)

Public Shared Sub Routine1(ByVal parm1 As String, ByVal parm2 As
Integer)
Debug.WriteLine(parm1, "routine1")
End Sub

Public Shared Sub Routine2(ByVal parm1 As String, ByVal parm2 As
Integer, ByVal parm3 As Double)
Debug.WriteLine(parm1, "routine2")
End Sub

Public Class ActionQueue

Private ReadOnly m_queue As New Queue

Public ReadOnly Property IsEmpty() As Boolean
Get
Return m_queue.Count = 0
End Get
End Property

Public Sub Enqueue(ByVal routine As AnyRoutine1)
m_queue.Enqueue(routine)
End Sub

Public Sub Enqueue(ByVal routine As AnyRoutine2)
m_queue.Enqueue(routine)
End Sub

Public Sub Process(ByVal parm1 As String, ByVal parm2 As Integer,
ByVal parm3 As Double)
Dim value As Object = m_queue.Dequeue()
If TypeOf value Is AnyRoutine1 Then
Dim routine As AnyRoutine1 = DirectCast(value, AnyRoutine1)
routine(parm1, parm2)
ElseIf TypeOf value Is AnyRoutine2 Then
Dim routine As AnyRoutine2 = DirectCast(value, AnyRoutine2)
routine(parm1, parm2, parm3)
End If
End Sub

End Class

Public Shared Sub Main()
Dim q As New ActionQueue
q.Enqueue(AddressOf Routine1)
q.Enqueue(AddressOf Routine2)


Do Until q.IsEmpty
q.Process("a", 1, 2)
Loop

End Sub


2) Use a class derived from an "argument" class such as EventArgs. The
Delegate would accept the "EventArgs" class, each "handler" would need to
check the type of EventArgs class to see number of parameters...

3) Define an action/command class that is placed in the queue, Specific
"functions" would derive from the base action/command class. This would be a
variation of the Command Pattern.

Public MustInherit Class Action

Public MustOverride Sub Routine()

End Class

Public Class Action1
Inherits Action

Public Sub New(ByVal parm1 As String, ByVal parm2 As Integer)

End Sub

Public Overrides Sub Routine()

End Sub

End Class

Public Class Action2
Inherits Action

Public Sub New(ByVal parm1 As String, ByVal parm2 As Integer, ByVal
parm3 As Double)

End Sub

Public Overrides Sub Routine()

End Sub

End Class

Public Class ActionQueue

Private ReadOnly m_queue As New Queue

Public ReadOnly Property IsEmpty() As Boolean
Get
Return m_queue.Count = 0
End Get
End Property

Public Sub Enqueue(ByVal routine As Action)
m_queue.Enqueue(routine)
End Sub

Public Sub Enqueue(ByVal parm1 As String, ByVal parm2 As Integer)
m_queue.Enqueue(New Action1(parm1, parm2))
End Sub

Public Sub Enqueue(ByVal parm1 As String, ByVal parm2 As Integer,
ByVal parm3 As Double)
m_queue.Enqueue(New Action2(parm1, parm2, parm3))
End Sub

Public Sub Process()
Dim value As Action = DirectCast(m_queue.Dequeue(), Action)
value.Routine()
End Sub

End Class


Public Shared Sub Main()
Dim q As New ActionQueue

Dim parm1 As String, parm2 As Integer, parm3 As Double
q.Enqueue(New Action1(parm1, parm2))
q.Enqueue(New Action2(parm1, parm2, parm3))

' alternatives
q.Enqueue(parm1, parm2)
q.Enqueue(parm1, parm2, parm3)

Do Until q.IsEmpty
q.Process()
Loop

End Sub

The first two have "polymorphism problems", in that they require you to
inquire of the type of delegate or type of parameter being passed. Using an
array or ParamArray would be a good 4th method. It really depends on what
other things are going on in the ActionQueue class...

Hope this helps
Jay
 
Ot,
Thinking about this.

You could actually combine my method 1 with method 3. Each derived class
could accept a delegate, that the Routine method would call. Thus gaining
the benefits of both methods.

If ActionQueue.Enqueue accepted a parameter array (that is ultimately passed
to the Delegate you could store the Delegate as Delegate itself, then use
Dan

Public Class ActionQueue

Public Sub Process(ParamArray ByVal args() As Object)
Dim routine As Delegate = DirectCast(m_queue.Dequeue(),
Delegate)
routine.DynamicInvoke(args)
End Sub

Public Sub Process()
Dim args() As Object
Dim routine As Delegate = DirectCast(m_queue.Dequeue(),
Delegate)
routine.DynamicInvoke(args)
End Sub

However I understand that DynamicInvoke is slower then Invoke itself.

Hope this helps
Jay
 
Thanks for your help, Jay. Your gift of your time is much appreciated as
always.

I settled for a variation of your solution 1. That is, I defined delegates
that can take 0 to 4 parameters and have Action classes (Action0, Action1,
etc.) to hold 0 to 4 parameters for the ProcessQueue method.

I have overloads for the Enqueue process for each number of parameters, and
put the right type of Action in the queue.

During ProcessQueue I peek at the top and determine which kind of Action
class is there and call the queued routine with the parameters saved in the
Action class.

Of course, I am limited to queuing routines with only 0 to 4 plain (not
array) parameters. This will fill my needs for now.

Thanks again,
Ot
 
Back
Top