棘手的VB / VBA副作用,以及如何避免它们 [英] Tricky VB/VBA side effects, and how to avoid them

查看:80
本文介绍了棘手的VB / VBA副作用,以及如何避免它们的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

== On Error Resume next,and Err.Number ==


如果你想从另一个程序中调用你的一个程序,并检查

之后的错误,你可能想象你应该写代码类似

这个...


错误重复下一步

MyFoo 123

lngErrNum = Err.Number

错误转到0


....所以你这样做,它似乎工作。过了一会儿,你在内部添加了类似于
MyFoo的代码,现在你发现上面的代码

有时会检测到错误。这是因为有时候,在

调用过程中跳过错误后,

错误信息永远不会被清除,因此当调用过程检查时它仍然存在它。


我建议在两端设计出这种可能性。首先,在On Error Resuse Next之后总是做一个明确的Err.Clear

第二节,

总是陷阱因使用On Error Goto< label>而不是On Error Resume在

相同项目中调用其他程序时出现错误接下来,尽管

所涉及的额外代码。


==将对象值传递给程序的Variant参数==


假设您有一个将Variant作为参数的过程,并将参数值添加到集合中。现在,让我们假设您从表单中的代码调用该程序

,并将其传递给我,例如,我!txtName作为参数,然后关闭

表单。之后,当您尝试从集合中读取值时,

您会收到错误消息。为什么?


为方便起见,当你将它们视为值时,VB允许某些对象表现为值。当你将它们视为对象时,VB允许它们作为对象引用

引用,因此我可以说strName = Me!txtName从控件获取值,或者设置ctlName = Me!txtName来引用控件。


当你将一个对象作为Variant参数传递时,没有任何关于

语法告诉VB是否使用该值或对象refence,以及在这种情况下,
将始终使用对象引用。这个工作正常,因为程序要么需要一个对象,要么在内部执行某些操作,这会导致对象的值解释在

对象的状态仍然有效。


这个问题真的很隐蔽,因为它可能会导致程序在

a变化之后中断无关紧要,并且可能导致代码工作一些

循环,而不是在其他情况下(例如,在测试中工作,当你向客户演示

时失败)。


再次,我建议在两端解决问题,只是为了覆盖你所有的b
。当你想将一个对象的值传递给一个过程时,请明确它,然后传递像Me!txtName.Value这样的东西。在编写一个

过程时,该过程采用变量并且只需要处理一个值,而不是一个

对象引用,将您获得的任何对象引用转换为普通值。 br />

Sub Foo(varValue As Variant)

Dim varUseValue As Variant

varUseValue = varValue

。 ..


==传递对象引用ByVal vs ByRef ==


鉴于上述问题,您可能认为可以通过以下方式解决问题定义

您的函数参数为ByVal。那应该总是得到

对象的值,而不是对象引用,对吗?错了。


这里有两个级别的比喻。


首先,对于通过引用传递的参数,该函数使用

间接引用返回调用过程中的变量,因此不需要复制

整个项目(对于字符串或者字符串来说是一件好事)

array),以及对值所做的任何更改。 (更多内容如下)

影响传递给调用代码中程序的变量值




其次,存在具有引用的对象类型变量。对象

存储在内存中的实例,但这些是不同的ByRef意味着通过

" references" ;.


事实证明,通过

引用或按值传递对象变量的实际意义是,如果通过引用传递它,则分配

参数变量a对新对象实例的引用也会影响调用过程中的变量

,因此它现在也指向新对象

实例。


当通过值传递时,在被调用的过程中创建对同一对象实例

的单独的新引用,以便对
$的状态进行更改b $ b实例仍在调用者和被调用过程之间共享(2

引用属于同一个实例),但是将新实例分配给

参数变量被调用的过程也不会影响调用过程中的

变量(2参考ces现在是不同的

实例)。


== ByRef和副作用==


参数in如果你没有指定,VB / VBA是ByRef,这意味着对被调用过程中的参数进行的更改会影响调用

过程。这里没有新消息。但是,这意味着你真的必须要小心,因为将参数作为变量使用

是一种初始状态,这是很常见的作为程序过程的一部分进行更改。

如果你不记得参数ByVal,你只是不小心

在调用中损坏了一个值程序,这种错误可能真的很难跟踪。


我建议对这个问题进行另一个双管齐下的攻击。首先,总是

为任何参数指定ByVal,没有特定的,有理由成为ByRef

(性能只是你有或期望有的一个很好的理由

一个实际的性能问题)。其次,永远不要为

参数变量赋值,除非它实际上是为了通过ByRef参数向调用过程返回值

。在所有其他情况下,对于在执行期间可以更改的任何值,使用

本地Dim''d变量

的程序。

解决方案

Steve Jorgensen< no **** @ nospam.nospam>写在

新闻:7s ******************************** @ 4ax.com:

再次,我建议在两端解决问题,只是为了覆盖你所有的基础。当你想要将一个对象的值传递给一个
过程时,要明确它,然后传递一些像我这样的东西!txtName.Value。当编写一个带变量的过程并且需要只处理一个值而不是一个对象引用时,
转换任何对象引用都会得到一个普通的值。

Sub Foo(varValue As Variant)
Dim varUseValue As Variant
varUseValue = varValue
...




将puts()放在一个对象导致评估值为

通过了吗?


使用ByVal也没有解决问题,假设你不想要

对值本身进行操作(无论如何你都无法使用.Value进行
)?


-

David W. Fenton http:// www。 bway.net/~dfenton

dfenton at bway dot net http://www.bway.net/~dfassoc


Steve Jorg ensen< no **** @ nospam.nospam>写在

新闻:7s ******************************** @ 4ax.com:

==传递对象引用ByVal vs ByRef ==

鉴于上述问题,您可能认为可以通过
定义您的问题来解决问题函数参数为ByVal。这应该总是得到对象的价值,而不是对象的引用,对吧?
错了。

这里有两个级别的隐喻。

首先,对于通过引用传递的参数,函数
使用间接引用返回调用
过程中的变量,因此不必复制整个项目(字符串或数组的好东西),以及对值的任何更改。 (以下更多内容)也会影响传递给调用代码中的过程的变量值。

其次,存在具有引用的对象类型变量。
存储在内存中的对象实例,但这些是不相同的东西ByRef意味着引用。

事实证明它实际意味着通过通过引用或按值变量的对象是,如果通过
引用传递它,则为参数变量赋值,对新的对象实例的引用会影响调用过程中的变量
当通过值传递时,在被调用的过程中创建对同一个对象实例的单独的新引用,这样对实例状态所做的更改仍然在调用者和被调用过程之间共享(2个引用是相同的
实例),但是为参数分配了一个新实例变量
在被调用的过程中也不会影响调用过程中的变量(2个引用现在是不同的
实例)。




史蒂夫,我不明白这一点 - 我不能明白你的意思。


-

David W. Fenton http://www.bway.net/~dfenton

dfenton at bway dot net http://www.bway。 net /~dfassoc


2004年9月1日星期三19:17:05 GMT,David W. Fenton

< dX ******** @ bway.net.invalid>写道:

Steve Jorgensen< no **** @ nospam.nospam>在
新闻中写道:7s ******************************** @ 4ax.com:

再次,我建议在两端解决问题,只是为了覆盖你所有的基础。当你想要将一个对象的值传递给一个
过程时,要明确它,然后传递一些像我这样的东西!txtName.Value。当编写一个带变量的过程并且需要只处理一个值而不是一个对象引用时,
转换任何对象引用都会得到一个普通的值。

Sub Foo(varValue As Variant)
Dim varUseValue As Variant
varUseValue = varValue
...
在对象周围put()会导致评估值被传递?




是的,但是当您只传递一个参数时,这会很奇怪。如果

这是一个Sub调用,看起来你放入不属于那里的括号,

如果你调用一个函数,它看起来像是骗你的有额外的设置。无论哪种方式,

你或其他维护代码的人可能会在以后把它们拿出来。

使用ByVal也不会解决问题,假设你没有不希望
对价值本身进行操作(无论如何,你真的无法用.Value做什么)?




在我的帖子的另一部分中,我尝试(显然不是很好)来解释

为什么事实证明它不会,尽管我只是理解这个

很短的时间,我自己。当涉及到对象时,ByVal被错误命名,因为它没有强制对象的值进行评估,它只是对一个新的

引用了对象而不是共享调用过程的参考

到对象。


如果这有意义,那么它在你的另一篇文章中回答了问题好吧。

或许,一个例子会有所帮助。


我们假设我的表格有控件txtValue1,包含aaa。和txtValue2

包含bbb。下面的代码并不像你实际要做的那样,但它说明了我试图解释的行为。


首先,这里这是以下代码的输出......


txtValue1 = aaa txtValue2 = bbb

txtValue1 = aaa txtValue2 = xxx

ctl.Name = txtValue2

txtValue1 = yyy txtValue2 = zzz

ctl.Name = txtValue2


现在,代码...


Private Sub Foo()

Dim ctl As Access.Control


Debug.Print" ; txtValue1 = QUOT; &安培;我!txtValue1,_

" txtValue2 =" &安培;我!txtValue2


设置ctl = Me!txtValue1

Bar ctl

''我!txtValue2现在包含xxx

''ctl现在包含对我的引用!txtValue2

Debug.Print" txtValue1 =" &安培;我!txtValue1,_

" txtValue2 =" &安培;我!txtValue2

Debug.Print" ctl.Name =" &安培; ctl.Name


设置ctl = Me!txtValue2

Baz ctl

''我!txtValue1现在包含xxx

''ctl仍然包含对我的引用!txtValue2

Debug.Print" txtValue1 =" &安培;我!txtValue1,_

" txtValue2 =" &安培;我!txtValue2

Debug.Print" ctl.Name =" &安培; ctl.Name


结束子


私人子栏(varValue As Variant)

''因为参数是通过引用,下一行更改

''控制调用代码'的参数变量指向

''。

设置varValue = Me!txtValue2

varValue.Value =" xxx"

End Sub


Private Sub Baz(ByVal varValue作为Variant)

''ByVal给了我们一个单独的本地引用对象,

''但它仍然是一个对象引用,而不是一个值。 br />
varValue.Value =" zzz"

''因为参数是ByVal,下一行只影响

''的状态本地varValue。

设置varValue = Me!txtValue1

varValue.Value =" yyy"

End Sub


== On Error Resume next, and Err.Number ==

If you want to call one of your procedures from another procedure, and check
for errors afterward, you mayimagine that you should write code something like
this...

On Error Resuse Next
MyFoo 123
lngErrNum = Err.Number
On Error Goto 0

.... so you do that, and it seems to work. A while later, you add code inside
MyFoo that does something similar, and now you find that the code above
sometimes detects an error when it shouldn''t. This is because sometimes, the
error information never does get cleared after the error is skipped in the
called procedure, so it''s still there when the calling procedure checks it.

I recommend engineering out this possibility at both ends. First, always do
an explicit Err.Clear after an "On Error Resuse Next" section, and second,
always trap for the occurrence of errors in calls to other procedures in the
same project using On Error Goto <label>, not On Error Resume Next, in spite
of the extra code involved.

== Passing object values to Variant parameters of procedures ==

Let''s say you have a procedure that takes a Variant as a parameter, and adds
the parameter value to a collection. Now, let''s say you call that procedure
from code in a form, and pass it, say, Me!txtName as an argument, then close
the form. Later, when you try to read the value back out of the collection,
you get an error. Why?

For convenience, VB allows certain objects to behave as values when you treat
them as values and as object references when you treat them as object
references, thus I can say either strName = Me!txtName to get the value from
the control, or Set ctlName = Me!txtName to make a reference to the control.

When you pass an object as a Variant parameter, there is nothing about the
syntax that tells VB whether to use the value or the object refence, and it
will always use the object reference in this case. This works fine only so
long as the procedure either expects an object, or does something internally
that causes the value interpretation of the object to be used while the
object''s state is still valid.

This problem is really insidious since it can cause procedures to break after
a change that seems inconsequential, and can cause code to work some
circumetances, and not in others (e.g. work in testing, and fail when you demo
to the customer).

Again, I recommend solving the problem at both ends, just to cover all your
bases. When you want to pass the value of an object to a procedure, be
explicit about it, and pass something like Me!txtName.Value. When writing a
procedure that takes a variant and needs to deal only with a value, not an
object reference, convert any object reference you get to a plain value.

Sub Foo(varValue As Variant)
Dim varUseValue As Variant
varUseValue = varValue
...

== Passing object references ByVal vs ByRef ==

Given the issue above, you may think you can solve the problem by defining
your function parameters as ByVal. That should always get the value of the
object, not the object reference, right? Wrong.

There are actually 2 levels of metaphor going on here.

First, for parameters that are passed by "reference", the function uses an
indirect reference back to the variable in the calling procedure, so the
entire item does not have to be copied (a good thing for a string or an
array), and so that any changes made to the "value" (more on this below)
affect the value of the variable passed to the procedure in the calling code
as well.

Second, there are object type variables that have "references" to object
instances stored in memory, but these are -not- the same thing ByRef means by
"references".

It turns out that what it actually means to pass an object variable by
reference or by value is that if you pass it by reference, assigning the
parameter variable a reference to a new object instance affects the variable
in the calling procedure as well, so that it now also points to the new object
instance.

When passing by value, a separate, new reference to the same object instance
is created in the called procedure, so that changes made to the state of the
instance are still shared between the caller and called procedure (the 2
references are to the same instance), but assigning a new instance to the
parameter variable within the called procedure does not also affect the
variable in the calling procedure (the 2 references are now to different
instances).

== ByRef and side effects ==

Parameters in VB/VBA are ByRef if you don''t specify, and that means that a
change made to the parameter in a called procedure affect the calling
procedure. No new news here. What that means, though, is that you really
have to be careful because it''s common to use a parameter as a variable with
an initial state that can then be changed as part of the procedure''s process.
if you don''t remember to make the parameter ByVal, you just accidentally
mangled a value in the calling procedure, and this kind of bug can be really
tricky to track down.

I recommend yet another 2-pronged attack on this problem. First, always
specify ByVal for any parameter that has no specific, good reason to be ByRef
(and performance is only a good reason when you are having or expect to have
an actual performance problem). Second, never, ever assign a value to a
parameter variable unless it actually is for the purpose of returning a value
to the calling procedure through a ByRef parameter. In all other cases, use
local Dim''d variables for any values that can be changed during the execution
of the procedure.

解决方案

Steve Jorgensen <no****@nospam.nospam> wrote in
news:7s********************************@4ax.com:

Again, I recommend solving the problem at both ends, just to cover
all your bases. When you want to pass the value of an object to a
procedure, be explicit about it, and pass something like
Me!txtName.Value. When writing a procedure that takes a variant
and needs to deal only with a value, not an object reference,
convert any object reference you get to a plain value.

Sub Foo(varValue As Variant)
Dim varUseValue As Variant
varUseValue = varValue
...



Does putting () around an object cause the evaluated value to be
passed?

Wouldn''t using ByVal fix the problem, too, assuming you don''t want
to operate on the value itself (which you wouldn''t really be able to
do with .Value, anyway)?

--
David W. Fenton http://www.bway.net/~dfenton
dfenton at bway dot net http://www.bway.net/~dfassoc


Steve Jorgensen <no****@nospam.nospam> wrote in
news:7s********************************@4ax.com:

== Passing object references ByVal vs ByRef ==

Given the issue above, you may think you can solve the problem by
defining your function parameters as ByVal. That should always
get the value of the object, not the object reference, right?
Wrong.

There are actually 2 levels of metaphor going on here.

First, for parameters that are passed by "reference", the function
uses an indirect reference back to the variable in the calling
procedure, so the entire item does not have to be copied (a good
thing for a string or an array), and so that any changes made to
the "value" (more on this below) affect the value of the variable
passed to the procedure in the calling code as well.

Second, there are object type variables that have "references" to
object instances stored in memory, but these are -not- the same
thing ByRef means by "references".

It turns out that what it actually means to pass an object
variable by reference or by value is that if you pass it by
reference, assigning the parameter variable a reference to a new
object instance affects the variable in the calling procedure as
well, so that it now also points to the new object instance.

When passing by value, a separate, new reference to the same
object instance is created in the called procedure, so that
changes made to the state of the instance are still shared between
the caller and called procedure (the 2 references are to the same
instance), but assigning a new instance to the parameter variable
within the called procedure does not also affect the variable in
the calling procedure (the 2 references are now to different
instances).



Steve, I don''t understand this -- I can''t quite get your point.

--
David W. Fenton http://www.bway.net/~dfenton
dfenton at bway dot net http://www.bway.net/~dfassoc


On Wed, 01 Sep 2004 19:17:05 GMT, "David W. Fenton"
<dX********@bway.net.invalid> wrote:

Steve Jorgensen <no****@nospam.nospam> wrote in
news:7s********************************@4ax.com :

Again, I recommend solving the problem at both ends, just to cover
all your bases. When you want to pass the value of an object to a
procedure, be explicit about it, and pass something like
Me!txtName.Value. When writing a procedure that takes a variant
and needs to deal only with a value, not an object reference,
convert any object reference you get to a plain value.

Sub Foo(varValue As Variant)
Dim varUseValue As Variant
varUseValue = varValue
...
Does putting () around an object cause the evaluated value to be
passed?



Yes, but that reads strangely when you are only passing one parameter. If
it''s a Sub call, it looks like you put in parenthes that don''t belong there,
and if you call a function, it looks liek you have an extra set. Either way,
you or someone else maintaining the code might end up taking them out later.
Wouldn''t using ByVal fix the problem, too, assuming you don''t want
to operate on the value itself (which you wouldn''t really be able to
do with .Value, anyway)?



In another section of my post, I tried (not very well, apparently) to explain
why it turns out that it would not, though I''ve only understood this for a
short time, myself. When objects are involved, ByVal is misnamed, because it
doesn''t force the object''s value to be evaluated, it just makes a new
reference to the object instead of sharing the calling procedure''s reference
to the object.

If that makes sense, then it answers the question in your other post as well.
Perhaps, an example would help, though.

Let''s assume my form has controls txtValue1, containing "aaa" and txtValue2
containing "bbb". The following code doesn''t resemble anything you would
actually do, but it illustrates the behavior I''m trying to explain.

First, here''s the output from the code below...

txtValue1=aaa txtValue2=bbb
txtValue1=aaa txtValue2=xxx
ctl.Name=txtValue2
txtValue1=yyy txtValue2=zzz
ctl.Name=txtValue2

Now, the code...

Private Sub Foo()
Dim ctl As Access.Control

Debug.Print "txtValue1=" & Me!txtValue1, _
"txtValue2=" & Me!txtValue2

Set ctl = Me!txtValue1
Bar ctl
'' Me!txtValue2 now contains "xxx"
'' ctl now contains a reference to Me!txtValue2
Debug.Print "txtValue1=" & Me!txtValue1, _
"txtValue2=" & Me!txtValue2
Debug.Print "ctl.Name=" & ctl.Name

Set ctl = Me!txtValue2
Baz ctl
'' Me!txtValue1 now contains "xxx"
'' ctl still contains a reference to Me!txtValue2
Debug.Print "txtValue1=" & Me!txtValue1, _
"txtValue2=" & Me!txtValue2
Debug.Print "ctl.Name=" & ctl.Name

End Sub

Private Sub Bar(varValue As Variant)
'' Because parameter is by reference, the next line changes
'' what control the calling code''s parameter variable points
'' to as well.
Set varValue = Me!txtValue2
varValue.Value = "xxx"
End Sub

Private Sub Baz(ByVal varValue As Variant)
'' ByVal gave us a separate, local reference to the object,
'' but it''s still an object reference, not a value.
varValue.Value = "zzz"
'' Because parameter is by ByVal, the next line only affects
'' the state of the local varValue.
Set varValue = Me!txtValue1
varValue.Value = "yyy"
End Sub


这篇关于棘手的VB / VBA副作用,以及如何避免它们的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆