我应该在 VB/VBA 中使用 Call 关键字吗?
Posted
技术标签:
【中文标题】我应该在 VB/VBA 中使用 Call 关键字吗?【英文标题】:Should I use Call keyword in VB/VBA? 【发布时间】:2011-02-04 03:22:33 【问题描述】:我在 VB/VBA 中调用 subs 时使用 Call
关键字。我知道它是可选的,但是使用它还是关闭它更好?我一直认为它更明确,但也许它只是噪音。
另外,我在另一个论坛上读到:使用Call
关键字更快,因为它知道它不会返回任何值,因此不需要设置任何堆栈空间来为返回腾出空间价值。
【问题讨论】:
在某些情况下使用 call 关键字非常有用,例如当您只想使用一个类时。我今天用它来生成随机盐Call New RNGCryptoServiceProvider().GetBytes(salt)
没有Call
我必须首先将变量作为RNGCryptoServiceProvider
这很容易成为我听说过的使用 call 的最佳(如果不是唯一的话)理由。
@Pillgram 除了RNGCryptoServiceProvider
继承了RandomNumberGenerator
,它实现了IDisposable
,所以Call
的这种“非常有用”的使用实际上是在分配非托管资源,并让它们悬空,因为@987654330 @ 永远不会被调用。在某些情况下,其后果可能是灾难性的。坏主意,IMO。
@yu_ominae 我也打算在这个上 ping 你 ^
@MathieuGuindon 这是一个非常好的观点,谢谢!
【参考方案1】:
没有人提及这一重要区别:在某些(常见)情况下,Call 防止函数(和子)参数周围的括号导致参数被严格解释为 ByVal
。
对您来说最大的收获是,如果您确实在例程的参数周围使用括号,可能是死记硬背或习惯,即使它们不是必需的,那么您应该使用 Call
以确保例程的隐式或显式 @ 987654325@ 不被忽视而有利于ByVal
;或者,您应该对返回值使用“等号”赋值来防止忽略(在这种情况下,您不会使用Call
)。
同样,这是为了保护您免于从常规中获取ByVal
。相反,当然,如果您想要 ByVal
解释而不考虑例程的声明,那么请不要使用 Call
(并使用括号)。
理由:总结"ByRef and ByVal Parameters"
如果 1.有一个函数调用retval的赋值,e。 G。
iSum = myfunc(myArg)
或
2. 使用“Call
”,例如。 g.
call myFunc(myArg)
或
call mySub(myArg)
然后括号严格描述调用参数列表;例程声明确定 ByVal 或 ByRef。否则,括号会强制例程使用 ByVal - 即使例程中未指定 ByVal。因此,
mySub(myArg) 'uses ByVal regardless of the routine's declaration, whereas
Call mySub(myArg) 'uses ByRef, unless routine declares ByVal
另请注意,Call 在语法上要求使用括号。你可以去
mySub myArg
但你不能去
call mySub myArg
但你可以去
call mySub(myArg)
(在语法上,括号是函数返回值的分配所必需的)
但请注意,例程声明中的ByVal
会覆盖所有这些。仅供参考,如果您保持沉默,ByRef
总是隐含在声明中;因此 TMK ByRef
除了纪录片之外没有任何明显的价值。
从上面重复:对您来说最大的收获是,如果您确实在例程的参数周围使用括号,可能是死记硬背或习惯,即使它们不是必需的,那么您应该使用 Call
来确保例程的隐式或显式 ByRef
不会被忽略以支持 ByVal
;或者,您应该对返回值使用“等号”赋值来防止忽略(在这种情况下,您不会使用Call
)。
同样,这是为了保护您免于从例行程序中获取ByVal
。相反,当然,如果您想要 ByVal
解释而不管例程的声明,那么请不要使用 Call
(并使用括号)。
【讨论】:
这个论点有点不对劲。括号不会神奇地将ByRef
变成ByVal
。括号强制对表达式求值,就像它们在每个其他表达式上下文中所做的一样。所以会发生什么,表达式被评估,其结果被传递给被调用的过程,它接收它ByRef
作为广告 - 只是,调用方没有任何东西持有 reference 到参数表达式的值,因此最终效果类似于传递ByVal
。我邀请您阅读this answer。
这是对过程的出色剖析。尽管如此,从机械的角度来看,从用户的角度来看,如果您添加“不需要的”括号,则参数 将 被视为 ByVal,即使您在函数本身上声明“ByRef”也是如此。对于那些不知道你指出的错综复杂的人来说,这是非常不直观的。我所说的是“括号强制 ByVal 被例程使用” - 这是一个真实的陈述,尽管正如您所指出的,括号引发了一个中间过程,并且该过程的结果是 ByVal 处理随之而来。
关于您提供的链接,它认为 Call 什么都不做(除了促进某些单行多命令)。我只是将其视为错误而拒绝,因为 myfunc(foo) 和 Call myfunc(foo) 会在 MyFunc 更改 foo 参数时严格产生两个不同的结果(除非 MyFunc 说“ByVal”)。
你错过了整点。 myFunc(foo)
不存在,它是 myFunc (foo)
(空格很重要!),隐式调用语句的正确语法是 myFunc foo
,不带括号。您正在使用 Call
作为正确理解语言语法的替代品。
VBE(与 Excel 无关)将 myFunc(foo)
转换为 myFunc (foo)
,并且无论您如何努力,都会继续添加该空间。您说的是另一条指令bar = myFunc(foo)
,其中括号分隔参数列表,而在myFunc (foo)
中,它们包含一个参数表达式。【参考方案2】:
我发现“call”有用的唯一情况是相当偶然的,关于一些特殊的运算符。
Dim c As IAsyncOperation(Of StartupTask) = StartupTask.GetAsync("Startup")
……
(Await c).Disable()
第二行出现语法错误,就像使用“New”运算符时遇到的错误一样。我真的不想要一个新变量,这对我来说太不优雅了。所以我尝试了:
DirectCast(Await c, StartupTask).Disable()
这在语法上是正确的。但随后 IDE 提示我“DirectCast”是不必要的,并进行了简化。是的,就是:
Call (Await c).Disable()
这就是我喜欢 VS2017 Preview 的原因。 ?
【讨论】:
【参考方案3】:我迟到了 7 年,但几分钟前我在 MSDN 上阅读内容时碰巧遇到了 Call
关键字。特别是,它被用来做我认为在 VB.NET(与 C# 相对)中不可能的事情——这与@FCastro 的回答有关。
Class Test
Public Sub DoSomething()
Console.WriteLine("doing something")
End Sub
End Class
Sub Main()
Call (New Test()).DoSomething()
End Sub
在奇怪的情况下,您不需要实际的对象实例但需要其方法之一,您可以使用Call
保存一行。请注意,当它是操作的右侧时,这是不必要的:
Class Test
Public Function GetSomething() As Integer
Return 0
End Function
End Class
Sub Main()
Dim x As Integer = (New Test()).GetSomething()
End Sub
【讨论】:
好玩!我的一部分想试试这个,我的另一部分害怕学习另一个坏习惯:-) @SlowLearner 我建议把它放在你的帽子里作为琐事。 VB 创造了足够多的坏习惯 :) 是的。我实际上正在使用 VBA 并且很有趣(AFAICT),它与在 VBA 中工作并不接近,但探索起来仍然很有趣。干杯, @SlowLearner 你担心是对的。如果您新建的对象实现IDisposable
并且没有正确处理它会泄漏非托管资源,那么这种懒惰的黑客攻击可能会产生灾难性的后果。例如,在 Rubberduck(VBIDE 的 COM 插件)代码库中,泄漏的非托管资源已转化为拆卸时的访问冲突异常。此外,我很确定从 CLR 的角度来看,对象会被分配无论如何,所以Call (New Thing()).Method()
确实只是引入难以追踪的错误的糟糕方式。【参考方案4】:
如果您阅读MSDN Support page for the Call Statement,对于 VBA 的特定情况,至少,它确实说 Call 是可选的,但是与它非常相关并且似乎没有人注意到的是引用行:
"如果您使用 Call 语法调用任何内部或用户定义的函数,则该函数的返回值将被丢弃。"
这就是调用远非无用的原因。假设您正在编写 Sub SupportTasks,它为您做了很多非常相关的事情 Main Subs(例如,它从文件中导入数据以供不同程序使用) .现在,请注意,由于 SupportTasks 正在读取外部数据,因此这些数据很可能不会成为标准,并且 sub 将无法履行其职责。 你是做什么的?
例如,如果出现问题,您可以使用返回 False 的布尔函数。不要调用 sub,而是调用内部的函数 SupportTasks 和 If 语句,如果出现异常,该语句将退出 Main sub:
If Not SupportTasks(SomeArgument) Then
Application.ScreenUpdating = True
Exit Sub
'Else continue the Main sub regularly without writing anything in here
End If
如果您想知道这与 Call 到底有什么关系,请考虑以下内容:在另一个子中,我调用 SupportTasks,但我不需要它返回布尔值(例如,我确定不会发生错误)。好吧,如果我不将它放在 If 语句中或将函数分配给无用的变量,VBA 将不会编译并返回错误(procedure call invalid blah blah blah must assign价值等等等等等等。)。这就是 Call 的用武之地!
Call SupportTasks(SomeArgument) '<< "Call Function" call doesn't return an error
如果您仍然认为它没用,请将其视为保持井井有条的资源。为许多过程共享的例程编写单独的过程可以使您的代码更短和更易于理解,尤其是在您编写非常大的应用程序时。例如,如果您的 IT 部门交付/实施真实系统的速度较慢,则基于 Excel-Access 集成构建的 ERP 可以更容易操作、修复和自定义...
总结一下,一些互联网智慧:
始终编写代码,就好像审查它的人是一个知道你住在哪里的凶残的精神病患者。
阿门。
【讨论】:
你可以简单地使用SupportTasks SomeArgument
,它编译得很好,不用Call
。
确实如此。它只是忽略返回值。猜猜那会使我的论文稍微偏离,不是吗?...
但是,由于您在使用 Call 时没有进行“等号”分配,因此您可以忽略分配并到达相同的位置(忽略 retval)。因此,“就这样”而言,Call 仅从纪录片的角度来看才有价值。即,以下行为可能在功能上相同: 1. 调用 myfunc(i) 2. myfunc(i) (但请参阅下面我对此线程的***回复!!根据函数声明,这可能会产生不同的 ByVal 或 ByRef 处理变量 i !! 所以 Call 与它的省略还有另一个有意义的值)
曾经编写的每条MsgBox "foo"
指令都在丢弃函数的返回值。我还没有在声称始终使用Call
的人编写的代码中看到Call MsgBox("foo")
。【参考方案5】:
我总是在 VBA 中使用Call
。对我来说,它只是看起来更干净。但是,我同意,这只是句法糖,这完全是个人喜好的领域。在过去的几年里,我可能遇到过十几个全职 VBA 人员,但没有一个人使用过Call
。这有一个额外的好处,即我总是知道哪个代码是我的。 :p
【讨论】:
【参考方案6】:啊哈。我一直想知道这一点,甚至阅读一本两英寸厚的关于 VBA 的书基本上都说不要使用它,除非您想使用 VBE 的 Find 功能轻松查找大型项目中的调用。
但我刚刚发现了另一个用途。
我们知道可以用冒号字符连接代码行,例如:
Function Test(mode as Boolean)
if mode = True then x = x + 1 : Exit Sub
y = y - 1
End Sub
但是,如果您在行首使用过程调用来执行此操作,VBE 会假定您指的是标签并删除任何缩进,将行与左边距对齐(即使按预期调用过程):
Function Test()
Function1 : Function2
End Function
使用 Call 语句可以在保持代码缩进的同时连接过程调用:
Function Test()
Call Function1 : Call Function2
End Function
如果你在上面的例子中不使用 Call 语句,VBE 将假定 "Function1" 是一个标签并在代码窗口中左对齐,即使它不会导致错误。
【讨论】:
+1 for a legitimage case...尽管我认为在一行中包含多个指令/函数调用是一种不好的做法。【参考方案7】:对于 VB6,如果有可能将其转换为 VB.NET,使用Call
意味着语法不会改变。 (在 VB.NET 中,方法调用需要括号。)(我个人认为这不值得费心——任何 .NET 转换器至少可以在需要时放入括号。我只是将其列为一个原因。)
否则它只是语法糖。
注意 Call
关键字在调用其他方法/函数时可能不会更快,因为函数无论如何都会返回它的值,并且 VB 不需要创建局部变量来接收它,即使 Call
未使用。
【讨论】:
【参考方案8】:不,每次调用只会添加 7 个字符,没有任何好处。
【讨论】:
【参考方案9】:我使用Call
来开发我可能会在 VB.NET 中使用的通用库函数的所有 VBA 开发。这允许我在所有 VB 风格之间使用复制和粘贴来移动代码。我这样做是为了避免代码编辑器在“格式化”或“漂亮打印”粘贴的代码时产生的语法错误。唯一的编辑通常是Set
语句包含/排除。
如果您没有计划将 VB/VBA 代码迁移到 VB.NET,则无需使用 Call
语句。
【讨论】:
以上是关于我应该在 VB/VBA 中使用 Call 关键字吗?的主要内容,如果未能解决你的问题,请参考以下文章