Enumerable.Where 可以对任何可枚举的事物进行任意过滤,它是在 委托 的帮助下完成的,这是 VBA 不知道或无法实现的。
警告以下是不适合生产使用的实验代码。它按原样提供用于教育目的。使用风险自负。
你可以 模拟它。请参阅代码审查中的 Wait, is this... LINQ? 和 Generating and calling code on the fly - 下面是我称为 Delegate 的类 - 请注意,它的 PredeclaredId 属性设置为 True,因此可以调用其 Create 工厂方法默认实例。它使用正则表达式库来解析函数的定义,并使用 VBE 可扩展性 API 库在给定字符串的情况下生成一个“匿名函数”,例如:
Set x = Delegate.Create("(x) => MsgBox(""Hello, "" & x & ""!"")")
x.Execute "Mug"
上面的代码生成并调用了这个函数:
Public Function AnonymousFunction(ByVal x As Variant) As Variant
AnonymousFunction = MsgBox("Hello, " & x & "!")
End Function
这会产生你所期望的:
委托类
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 As MatchCollection
Set regexMatches = regex.Execute(expression)
If regexMatches.Count = 0 Then
Err.Raise 5, "Delegate", "Invalid anonymous function expression."
End If
Dim regexMatch As Match
For Each regexMatch In regexMatches
If regexMatch.SubMatches(0) = vbNullString Then
result.Body = methodName & " = " & Right(expression, Len(expression) - 6)
Else
Dim params() As String
params = Split(regexMatch.SubMatches(0), ",")
Dim i As Integer
For i = LBound(params) To UBound(params)
result.AddParameter Trim(params(i))
Next
result.Body = methodName & " = " & regexMatch.SubMatches(1)
End If
Next
Set Create = result
End Function
Public Function Execute(ParamArray params()) As Variant
On Error GoTo CleanFail
Dim paramCount As Integer
paramCount = UBound(params) + 1
GenerateAnonymousMethod
'cannot break beyond this point
Select Case paramCount
Case 0
Execute = Application.Run(methodName)
Case 1
Execute = Application.Run(methodName, params(0))
Case 2
Execute = Application.Run(methodName, params(0), params(1))
Case 3
Execute = Application.Run(methodName, params(0), params(1), params(2))
Case 4
Execute = Application.Run(methodName, params(0), params(1), params(2), _
params(3))
Case 5
Execute = Application.Run(methodName, params(0), params(1), params(2), _
params(3), params(4))
Case 6
Execute = Application.Run(methodName, params(0), params(1), params(2), _
params(3), params(4), params(5))
Case 7
Execute = Application.Run(methodName, params(0), params(1), params(2), _
params(3), params(4), params(5), _
params(6))
Case 8
Execute = Application.Run(methodName, params(0), params(1), params(2), _
params(3), params(4), params(5), _
params(6), params(7))
Case 9
Execute = Application.Run(methodName, params(0), params(1), params(2), _
params(3), params(4), params(5), _
params(6), params(7), params(8))
Case 10
Execute = Application.Run(methodName, params(0), params(1), params(2), _
params(3), params(4), params(5), _
params(6), params(7), params(8), _
params(9))
Case Else
Err.Raise 5, "Execute", "Too many parameters."
End Select
CleanExit:
DestroyAnonymousMethod
Exit Function
CleanFail:
Resume CleanExit
End Function
Friend Sub AddParameter(ByVal paramName As String)
this.Parameters.Add "ByVal " & paramName & " As Variant"
End Sub
Private Sub GenerateAnonymousMethod()
Dim component As VBComponent
Set component = Application.VBE.VBProjects("Reflection").VBComponents("AnonymousCode")
Dim params As String
If this.Parameters.Count > 0 Then
params = Join(Enumerable.FromCollection(this.Parameters).ToArray, ", ")
End If
Dim signature As String
signature = "Public Function " & methodName & "(" & params & ") As Variant" & vbNewLine
Dim content As String
content = vbNewLine & signature & this.Body & vbNewLine & "End Function" & vbNewLine
component.CodeModule.DeleteLines 1, component.CodeModule.CountOfLines
component.CodeModule.AddFromString content
End Sub
Private Sub DestroyAnonymousMethod()
Dim component As VBComponent
Set component = Application.VBE.VBProjects("Reflection").VBComponents("AnonymousCode")
component.CodeModule.DeleteLines 1, component.CodeModule.CountOfLines
End Sub
您需要将VBProjects("Reflection").VBComponents("AnonymousCode") 更改为指向您的VBA 项目中的某个空标准模块...或者为Execute 方法创建一个名为Reflection 的项目和一个名为AnonymousCode 的空标准模块将函数生成到。
作为 VBA 代码如何编译的工件,生成的代码可以执行,但您不能在其中放置断点,并且 VBE 将拒绝在生成的代码内部中断 - 所以无论您提供工厂的任何字符串方法,您最好确保它足够简单,可以 100% 无错误。
这给你的是一个封装特定动作的对象:这个对象可以作为参数传递,就像任何其他对象一样 - 所以如果你有自己的集合类实现(这里LinqEnumerable),那么你可以用它来实现一个Where方法,它接受一个Delegate参数,假设predicate参数封装了一个返回Boolean的函数:
Public Function Where(ByVal predicate As Delegate) As LinqEnumerable
Dim result As LinqEnumerable
Set result = New LinqEnumerable
Dim element As Variant
For Each element In encapsulated
If predicate.Execute(element) Then result.Add element
Next
Set Where = result
End Function
因此,给定自定义集合类,您可以创建一个定义自定义条件的Delegate 实例,将其传递给Where 方法,然后返回过滤结果。
您甚至可以进一步推动它并实现Aggregate 方法:
Public Function Aggregate(ByVal accumulator As Delegate) As Variant
Dim result As Variant
Dim isFirst As Boolean
Dim value As Variant
For Each value In encapsulated
If isFirst Then
result = value
isFirst = False
Else
result = accumulator.Execute(result, value)
End If
Next
Aggregate = result
End Function
并像使用 C# LINQ 一样运行它,减去编译时类型安全和延迟执行:
Dim accumulator As Delegate
Set accumulator = Delegate.Create("(work,value) => value & "" "" & work")
Debug.Print LinqEnumerable.FromList(List.Create("the", "quick", "brown", "fox")) _
.Aggregate(accumulator)
输出:
fox brown quick the
这项工作是 GitHub 上 VBEX 存储库中 Lambda 内容的基础(最初由 Rubberduck 项目的联合创始人 Chris McClellan 提供;不过大部分工作可以归功于 Philip Wales)- 100%-VBA 项目,为您提供其他几个类可供使用。我鼓励您探索这些,看看是否有更适合生产使用。