【问题标题】:VBA - workaround for conditionally calling a function with certain optional parameters?VBA - 有条件地调用具有某些可选参数的函数的解决方法?
【发布时间】:2015-08-04 11:40:32
【问题描述】:

考虑一个带有多个可选参数的函数。例如:

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

如果我只想在参数不为空或其他测试的情况下调用特定函数(因为函数会引发错误,否则除非参数丢失)。

Collection 类就是一个例子。 Add 方法有 3 个可选参数。许多自定义类包装了这个类 - 例如创建一个“Person”自定义类的可索引类 Persons 或类似的东西。包装器必须处理 6 种组合:仅添加 Key,添加 Key 并指定 Before,添加 Key 并指定 After,不添加 Key 并指定 Before,不添加 Key 并指定 After,无 Key 且无 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 ...

显然,对于每个附加的可选变量,嵌套 If 的数量和一般代码开销大约翻倍。

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

就像在 C# 中一样,它可以像以下那样干净:

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

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

如果它被实现,在 VBA 中可以使用内联 iff ('IIF(boolean, true, false)') 实现等价物

是否有我缺少的 VBA 解决方法?

【问题讨论】:

  • 你试过用ParamArray吗?
  • 除非每个参数都有不同的类型(整数、布尔值、字符串等),你怎么想知道哪个参数是哪个?你想如何管理 Foo() 中的参数?
  • 您已将每个参数定义为变体。因此,您可以使用IsMissing(var) 函数检查foo 函数内部是否缺少参数。请参阅:msdn.microsoft.com/en-us/library/office/gg251721.aspx
  • @MaciejLos 这是对外部函数的调用(即不是自定义函数)。我无法控制函数如何处理参数,只能控制传入的参数。丢失的参数很好,但如果指定了它,那么它不能是 null/nothing/任何我能想到的我可以使用的东西内联 if。
  • 嗯 - 您可能可以通过使用按名称调用来解决问题

标签: vba


【解决方案1】:

(编辑中:我合并了 HarveyFrench 对代码的改进)

你可以减少嵌套:

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 个案例,但它似乎有效。我所做的有点机械。您可以编写一个调度生成器 - 一个函数,它接受一个字符串函数名称、一个必需参数数组、一个可选参数数组和一个扮演Null角色的值,并以字符串形式返回调度程序,它可以从即时窗口复制粘贴到代码模块。我想在这里这样做,但对于概念验证调度程序来说似乎不值得。

【讨论】:

  • 请在我的回答中看到我的 cmets。我建议您可以修改您的代码以纳入我的建议
  • @HarveyFrench 感谢您的建议。它要求 foo_dispatcher 本身的参数现在是Optional,这是一个更好的变化。 foo_dispatcher(,,,) 是语法错误,但 foo_dispatcher() 工作正常。
  • 我对 Null 被视为缺失值或空值感到有些不安。我个人认为 foo 如果它接受变体,应该处理作为值传递的 Null !很好的代码来解决“糟糕的 vba”
  • @HarveyFrench null 是一个例子,因此我可以强调与 VBA 相比,使用 null coelescing 运算符与干净的 Type.Missing 的明显差异。最终的方法是解决整个“缺失”的愚蠢问题——我自己的类都没有使用缺失,而是总是使用类型本身的特性(Nothing、Empty、Null 等)。实际上,它正在寻找某种方法来避免显式设置调用具有指定不同可选参数的方法的各种组合。鉴于我们必须处理我们所拥有的,我认为您的回答是简化它的好方法。
  • 感谢您的支持 - 一种使不优雅更加流线型并大大优于嵌套 Ifs 的体面方法。我调查了 CallByName 但没有结果,因为在这种情况下它无法处理可选参数。 VB6 似乎有办法(使用调用程序),但 VBA 不能使用它。
【解决方案2】:

John Coleman 的回答是“与 VBA 一样好”

我认为 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

【讨论】:

  • 那是对代码的一个很好的修改。我会添加它(最后两个 4 替换为预期的 2 和 1)
  • 我很高兴有人醒了!
  • 感谢您的贡献。请参阅上面的我的 cmets,并将您所说的联合努力标记为“VBA 会得到很好”:)
猜你喜欢
  • 2017-09-02
  • 1970-01-01
  • 1970-01-01
  • 2019-10-22
  • 1970-01-01
  • 1970-01-01
  • 2014-09-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多