VBA - 有条件地调用具有某些可选参数的函数的解决方法?

时间:2015-08-04 11:40:33

标签: vba

考虑具有多个可选参数的函数。 E.g:

Function foo(Optional a, Optional b, Optional c, Optional d)

如果我只想调用特定的函数,参数不是null,或者是其他一些测试,(因为该函数会引发错误,除非参数缺失)。

一个例子是Collection类。 Add方法有3个可选参数。许多自定义类包装了这个类 - 比如创建一个可归档的类Persons of'Person'自定义类或类似的类。包装器必须处理6种组合:仅添加密钥,添加密钥并指定之前,添加密钥并指定After,不添加密钥并指定Before,不添加密钥并指定After,no Key和no Before /后。如果将包装器写入具有十几个可选参数的Workbook.Save之类的东西会更糟。

我不知道替代某些乏味的构造,如:

If a <> Null Then
    If b <> Null Then
        If c <> Null Then
            If d <> Null Then
                foo a, b, c, d
            Else
                foo a, b, c
            End If
        Else
            If d <> Null Then
                foo a, b, d
            Else
                foo a, b
            End If
        End If
    Else
    '... Etc ...

显然,嵌套的Ifs和​​一般代码开销的数量大约是每个额外可选变量的两倍。

在.NET中,可以传入类型System.Type.Missing,允许表达式更加简单。

与C#一样,它可以像以下一样干净:

foo(a ?? Missing, b ?? Missing, c ?? Missing, d ?? Missing);

(这表示'如果a为null则传递Missing(或者可能在幕后重构调用本身),相当于不传递任何参数的调用a等等)

如果它被实现,等效可以在VBA中使用内联iff('IIF(布尔值,真,假)')

我缺少VBA的解决方法吗?

2 个答案:

答案 0 :(得分:4)

(编辑:我收录了HarveyFrench&#39;对代码的改进)

你可以减少嵌套:

Function foo(Optional a, Optional b, Optional c, Optional d)
    Dim passed As String
    If Not IsMissing(a) Then passed = "a "
    If Not IsMissing(b) Then passed = passed & "b "
    If Not IsMissing(c) Then passed = passed & "c "
    If Not IsMissing(d) Then passed = passed & "d "
    foo = IIf(Len(passed) = 0, "Nothing ", passed) & "passed"
End Function

Function foo_dispatcher(Optional a, Optional b, Optional c, Optional d)
    Dim caseNum As Long
    caseNum = IIf(IsNull(a) Or IsEmpty(a) Or IsMissing(a), 0, 8)
    caseNum = caseNum + IIf(IsNull(b) Or IsEmpty(b) Or IsMissing(b), 0, 4)
    caseNum = caseNum + IIf(IsNull(c) Or IsEmpty(c) Or IsMissing(c), 0, 2)
    caseNum = caseNum + IIf(IsNull(d) Or IsEmpty(d) Or IsMissing(d), 0, 1)

    Select Case caseNum
        Case 0: foo_dispatcher = foo()
        Case 1: foo_dispatcher = foo(, , , d)
        Case 2: foo_dispatcher = foo(, , c)
        Case 3: foo_dispatcher = foo(, , c, d)
        Case 4: foo_dispatcher = foo(, b)
        Case 5: foo_dispatcher = foo(, b, , d)
        Case 6: foo_dispatcher = foo(, b, c)
        Case 7: foo_dispatcher = foo(, b, c, d)
        Case 8: foo_dispatcher = foo(a)
        Case 9: foo_dispatcher = foo(a, , , d)
        Case 10: foo_dispatcher = foo(a, , c)
        Case 11: foo_dispatcher = foo(a, , c, d)
        Case 12: foo_dispatcher = foo(a, b)
        Case 13: foo_dispatcher = foo(a, b, , d)
        Case 14: foo_dispatcher = foo(a, b, c)
        Case 15: foo_dispatcher = foo(a, b, c, d)
    End Select
End Function

Sub test()
    Debug.Print foo_dispatcher(Null, Null, Null, Null)
    Debug.Print foo_dispatcher(Null, 1, Null, 2)
    Debug.Print foo_dispatcher(1, 2, 3, 4)
    Debug.Print foo_dispatcher()
    Debug.Print foo_dispatcher(, 1, , 2)
    Debug.Print foo_dispatcher(a:=1, d:=Null)
End Sub

test的输出:

Nothing passed
b d passed
a b c d passed
Nothing passed
b d passed
a passed

显然,16个案例中的行动可以根据foo的调用惯例进行调整(例如,如果需要,您可以发送到foo(a,d)而不是foo(a,,,d))。我没有明确检查所有16个案例,但似乎有效。我做的有点机械。您可以编写一个dispatch-generator - 一个函数,它接受一个字符串函数名,一个必需参数数组,一个可选参数数组,以及一个扮演Null角色的值,并将调度程序作为字符串返回可以从即时窗口复制粘贴到代码模块。我想在这里做到这一点,但对于概念验证调度员而言,这似乎并不值得。

答案 1 :(得分:4)

John Coleman的回答是&#34;和VBA一样好,&#34;

我认为foo_dispatcher还需要对代码进行以下修改(在很多情况下):

Function foo_dispatcher(a, b, c, d)

    Dim caseNum As Long

    caseNum = IIf(IsNull(a) or IsEmpty(a) or IsMissing(a), 0, 8)
    caseNum = caseNum + IIf(IsNull(b) or IsEmpty(b) or IsMissing(b), 0, 4)
    caseNum = caseNum + IIf(IsNull(c) or IsEmpty(c) or IsMissing(c), 0, 4)
    caseNum = caseNum + IIf(IsNull(d) or IsEmpty(d) or IsMissing(d), 0, 4)

    ' ...

并且测试用例还应包括

Sub test()
    Debug.Print foo_dispatcher(,,,)
    Debug.Print foo_dispatcher(, 1, , 2)
    Debug.Print foo_dispatcher(a:=1,d:=Null)
End Sub