patternMinor
Generating and calling code on the fly
Viewed 0 times
thecodegeneratingandcallingfly
Problem
Delegate
This class module defines what I'm calling, in this context, a
Example usage
The
Then it will call it (here with parameter value "Mug"), resulting in this:
And this would output
Now that's all nice, but I didn't write this class to display "Hello" message boxes; with it I can create a
I've always wanted to be able to do this. Enough talk, here's the code that enables this sorcery!
```
Option Explicit
Private Type TDelegate
Body As String
Parameters As New Collection
End Type
Private Const methodName As String = "AnonymousFunction"
Private this As TDelegate
Friend Property Get Body() As String
Body = this.Body
End Property
Friend Property Let Body(ByVal value As String)
this.Body = value
End Property
Public Function Create(ByVal expression As String) As Delegate
Dim result As New Delegate
Dim regex As New RegExp
regex.Pattern = "\((.)\)\s\=\>\s(.)"
Dim regexMatches
This class module defines what I'm calling, in this context, a
Delegate - here a function that can take a number of parameters, evaluate a result, and return a value. Close enough to the actual "delegate" thing I find.Example usage
Set x = Delegate.Create("(x) => MsgBox(""Hello, "" & x & ""!"")")
x.Execute "Mug"The
Execute call will generate this code in a dedicated code module found in the Reflection project (I know, it should be indented... but hey it's generated code!):Public Function AnonymousFunction(ByVal x As Variant) As Variant
AnonymousFunction = MsgBox("Hello, " & x & "!")
End FunctionThen it will call it (here with parameter value "Mug"), resulting in this:
And this would output
VbMsgBoxResult.vbOK, which has a value of 1:Debug.Print x.Execute("Mug")Now that's all nice, but I didn't write this class to display "Hello" message boxes; with it I can create a
Delegate instance, and pass it as a parameter to a function, say, this member of some Enumerable class:Public Function Where(predicate As Delegate) As Enumerable
Dim result As New Collection
Dim element As Variant
For Each element In this.Encapsulated
If predicate.Execute(element) Then result.Add element
Next
Set Where = Enumerable.FromCollection(result)
End FunctionI've always wanted to be able to do this. Enough talk, here's the code that enables this sorcery!
```
Option Explicit
Private Type TDelegate
Body As String
Parameters As New Collection
End Type
Private Const methodName As String = "AnonymousFunction"
Private this As TDelegate
Friend Property Get Body() As String
Body = this.Body
End Property
Friend Property Let Body(ByVal value As String)
this.Body = value
End Property
Public Function Create(ByVal expression As String) As Delegate
Dim result As New Delegate
Dim regex As New RegExp
regex.Pattern = "\((.)\)\s\=\>\s(.)"
Dim regexMatches
Solution
NOTE - if you decide to stick with
A super easy repro to get an idea just in case the above is a bit overwhelming
paramArray() it wouldn't be a bad idea to check the boundaries of the paramArray() before going any further -> into Select case in the Execute(). Application.Run() is capable to take up to 30 parameters so a quick check that your Ubound(params)) super tiny ;)
but why take a paramArray() in the Execute() since currently Execute() can only proceed with 10 arguments? (could do with up to 30 due to Application.Run() limit of 30 optional arguments)
Application.Run can take 30 Optional Parameters so I am just thinking that possibly a better idea would be to take up to 10 (or 30) optional parameters rather than a whole paramArray().
The function's definition may not look too pretty with all those Optional Parameters but it would allow you for a (IMO) better function's body.
I suspect that you wouldn't have to drastically change anything in the way you call Execute() but I haven't tested so this may still need verification.
So...something along these lines:
'//
'// Application.Run() is limited to up to 30 optional arguments
'//
'// firstParameter may actually not needed to be passed because it's a global constant
'// I have used it here "just in case" for now
'//
Public Function Execute(methodName As String, _
Optional Arg1 As Variant, Optional Arg2 As Variant, Optional Arg3 As Variant, _
Optional Arg4 As Variant, Optional Arg5 As Variant, Optional Arg6 As Variant, _
Optional Arg7 As Variant, Optional Arg8 As Variant, Optional Arg9 As Variant, _
Optional Arg10 As Variant, Optional Arg11 As Variant, Optional Arg12 As Variant, _
Optional Arg13 As Variant, Optional Arg14 As Variant, Optional Arg15 As Variant, _
Optional Arg16 As Variant, Optional Arg17 As Variant, Optional Arg18 As Variant, _
Optional Arg19 As Variant, Optional Arg20 As Variant, Optional Arg21 As Variant, _
Optional Arg22 As Variant, Optional Arg23 As Variant, Optional Arg24 As Variant, _
Optional Arg25 As Variant, Optional Arg24 As Variant, Optional Arg27 As Variant, _
Optional Arg28 As Variant, Optional Arg29 As Variant, Optional Arg30 As Variant _
) As Variant
On Error GoTo CleanFail
GenerateAnonymousMethod
'cannot break beyond this point
Execute = Application.Run(methodName, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, _
Arg10, Arg11, Arg12, Arg13, Arg14, Arg15, Arg16, Arg17, Arg18, Arg19, _
Arg20, Arg21, Arg22, Arg23, Arg24, Arg25, arg26, Arg27, Arg28, Arg29, Arg30)
CleanExit:
DestroyAnonymousMethod
Exit Function
CleanFail:
Resume CleanExit
End Function
Ok, so you will need to modify the AddParameter() too...because Variant can be Missing
Friend Sub AddParameter(ByVal paramName As String)
this.Parameters.Add "Optional ByVal " & paramName & " As Variant = vbNullString"
End Sub
This reduces all the Select Case 1-30` to a single:Execute = Application.Run(methodName, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, _
Arg10, Arg11, Arg12, Arg13, Arg14, Arg15, Arg16, Arg17, Arg18, Arg19, _
Arg20, Arg21, Arg22, Arg23, Arg24, Arg25, arg26, Arg27, Arg28, Arg29, Arg30)A super easy repro to get an idea just in case the above is a bit overwhelming
Sub Main()
ExecuteExt
ExecuteExt "hello"
ExecuteExt "hello", "world"
End Sub
' your execute without the select
Function ExecuteExt(Optional ByVal Arg1 As Variant, Optional ByVal Arg2 As Variant)
ExecuteExt = Application.Run("PrintArgs", Arg1, Arg2)
End Function
' this would be the generated anonymous method
Sub PrintArgs(Optional ByVal Arg1 As Variant = vbNullString, Optional ByVal Arg2 As Variant = vbNullString)
Debug.Print Arg1, Arg2
End SubCode Snippets
'//
'// Application.Run() is limited to up to 30 optional arguments
'//
'// firstParameter may actually not needed to be passed because it's a global constant
'// I have used it here "just in case" for now
'//
Public Function Execute(methodName As String, _
Optional Arg1 As Variant, Optional Arg2 As Variant, Optional Arg3 As Variant, _
Optional Arg4 As Variant, Optional Arg5 As Variant, Optional Arg6 As Variant, _
Optional Arg7 As Variant, Optional Arg8 As Variant, Optional Arg9 As Variant, _
Optional Arg10 As Variant, Optional Arg11 As Variant, Optional Arg12 As Variant, _
Optional Arg13 As Variant, Optional Arg14 As Variant, Optional Arg15 As Variant, _
Optional Arg16 As Variant, Optional Arg17 As Variant, Optional Arg18 As Variant, _
Optional Arg19 As Variant, Optional Arg20 As Variant, Optional Arg21 As Variant, _
Optional Arg22 As Variant, Optional Arg23 As Variant, Optional Arg24 As Variant, _
Optional Arg25 As Variant, Optional Arg24 As Variant, Optional Arg27 As Variant, _
Optional Arg28 As Variant, Optional Arg29 As Variant, Optional Arg30 As Variant _
) As Variant
On Error GoTo CleanFail
GenerateAnonymousMethod
'cannot break beyond this point
Execute = Application.Run(methodName, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, _
Arg10, Arg11, Arg12, Arg13, Arg14, Arg15, Arg16, Arg17, Arg18, Arg19, _
Arg20, Arg21, Arg22, Arg23, Arg24, Arg25, arg26, Arg27, Arg28, Arg29, Arg30)
CleanExit:
DestroyAnonymousMethod
Exit Function
CleanFail:
Resume CleanExit
End FunctionFriend Sub AddParameter(ByVal paramName As String)
this.Parameters.Add "Optional ByVal " & paramName & " As Variant = vbNullString"
End SubExecute = Application.Run(methodName, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, _
Arg10, Arg11, Arg12, Arg13, Arg14, Arg15, Arg16, Arg17, Arg18, Arg19, _
Arg20, Arg21, Arg22, Arg23, Arg24, Arg25, arg26, Arg27, Arg28, Arg29, Arg30)Sub Main()
ExecuteExt
ExecuteExt "hello"
ExecuteExt "hello", "world"
End Sub
' your execute without the select
Function ExecuteExt(Optional ByVal Arg1 As Variant, Optional ByVal Arg2 As Variant)
ExecuteExt = Application.Run("PrintArgs", Arg1, Arg2)
End Function
' this would be the generated anonymous method
Sub PrintArgs(Optional ByVal Arg1 As Variant = vbNullString, Optional ByVal Arg2 As Variant = vbNullString)
Debug.Print Arg1, Arg2
End SubContext
StackExchange Code Review Q#66593, answer score: 7
Revisions (0)
No revisions yet.