变量自身迭代 - 不同类型的不同行为 [英] Variable iterating on itself - different behavior with different types

查看:36
本文介绍了变量自身迭代 - 不同类型的不同行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请查看帖子末尾的最新更新.

特别是,请参阅更新 4:变体比较诅咒

我已经看到伙伴们用头撞墙以了解变体的工作原理,但从未想过我会遇到自己的糟糕时刻.

I’ve already seen mates banging their head against the wall to understand how a variant works, but never imagined that I will have my own bad moment with it.

我已经成功地使用了以下 VBA 结构:

I have successfully used the following VBA construction:

对于 i = 1 到 i

iInteger 或任何数字类型时,这非常有效,从 1 迭代到 i<的原始值/代码>.我会在 iByVal 参数的情况下这样做 - 你可能会说懒惰 - 以免自己声明一个新变量.

This works perfectly when i is an Integer or any numeric type, iterating from 1 to the original value of i. I do this on occasions where i is a ByVal parameter - you might say lazy - to spare myself the declaration of a new variable.

然后,当这个构造停止"按预期工作时,我遇到了一个错误.经过一些艰苦的调试,我发现当 i 没有声明为显式数字类型,而是一个 Variant 时,它的工作方式并不相同.问题是双重的:

Then I had a bug when this construct "stopped" working as expected. After some hard debugging, I found that it doesn’t work the same way when i is not declared as explicit numeric type, but a Variant. The question is twofold:

1- ForFor Each 循环的确切语义是什么?我的意思是编译器执行的操作顺序是什么?例如,限制的评估是否先于计数器的初始化?在循环开始之前,这个限制是否被复制并固定"在某个地方?等等.同样的问题适用于For Each.

1- What are the exact semantics of the For and the For Each loops? I mean what is the sequence of actions that the compiler undertakes and in which order? For example, does the evaluation of the limit precede the initialization of the counter? Is this limit copied and "fixed" somewhere before the loop starts? Etc. The same question applies to For Each.

2- 如何解释变体和显式数字类型的不同结果?有人说变体是(不可变的)引用类型,这个定义能解释观察到的行为吗?

2- How to explain the different outcomes on variants and on explicit numeric types? Some say a variant is an (immutable) reference type, can this definition explain the observed behavior?

我已经为涉及 For 的不同(独立)场景准备了一个 MCVE>For Each 语句,结合整数、变量和对象.令人惊讶的结果促使明确定义语义,或者至少检查这些结果是否符合定义的语义.

I have prepared an MCVE for different (independent) scenarios involving the For and the For Each statements, combined with integers, variants and objects. The surprising results urge for defining unambiguously the semantics or, for the least, check if those results do conform to the defined semantics.

欢迎所有见解,包括解释一些令人惊讶的结果或其矛盾的部分见解.

All insights are welcome, including partial ones that explain some of the surprising results or their contradictions.

谢谢.

Sub testForLoops()
    Dim i As Integer, v As Variant, vv As Variant, obj As Object, rng As Range

    Debug.Print vbCrLf & "Case1 i --> i    ",
    i = 4
    For i = 1 To i
        Debug.Print i,      ' 1, 2, 3, 4
    Next

    Debug.Print vbCrLf & "Case2 i --> v    ",
    v = 4
    For i = 1 To v  ' (same if you use a variant counter: For vv = 1 to v)
        v = i - 1   ' <-- doesn't affect the loop's outcome
        Debug.Print i,          ' 1, 2, 3, 4
    Next

    Debug.Print vbCrLf & "Case3 v-3 <-- v ",
    v = 4
    For v = v To v - 3 Step -1
       Debug.Print v,           ' 4, 3, 2, 1
    Next

    Debug.Print vbCrLf & "Case4 v --> v-0 ",
    v = 4
    For v = 1 To v - 0
        Debug.Print v,          ' 1, 2, 3, 4
    Next

    '  So far so good? now the serious business

    Debug.Print vbCrLf & "Case5 v --> v    ",
    v = 4
    For v = 1 To v
        Debug.Print v,          ' 1      (yes, just 1)
    Next

    Debug.Print vbCrLf & "Testing For-Each"

    Debug.Print vbCrLf & "Case6 v in v[]",
    v = Array(1, 1, 1, 1)
    i = 1
    ' Any of the Commented lines below generates the same RT error:
    'For Each v In v  ' "This array is fixed or temporarily locked"
    For Each vv In v
        'v = 4
        'ReDim Preserve v(LBound(v) To UBound(v))
        If i < UBound(v) Then v(i + 1) = i + 1 ' so we can alter the entries in the array, but not the array itself
        i = i + 1
         Debug.Print vv,            ' 1, 2, 3, 4
    Next

    Debug.Print vbCrLf & "Case7 obj in col",
    Set obj = New Collection: For i = 1 To 4: obj.Add Cells(i, i): Next
    For Each obj In obj
        Debug.Print obj.Column,    ' 1 only ?
    Next

    Debug.Print vbCrLf & "Case8 var in col",
    Set v = New Collection: For i = 1 To 4: v.Add Cells(i, i): Next
    For Each v In v
        Debug.Print v.column,      ' nothing!
    Next

    ' Excel Range
    Debug.Print vbCrLf & "Case9 range as var",
    ' Same with collection? let's see
    Set v = Sheet1.Range("A1:D1") ' .Cells ok but not .Value => RT err array locked
    For Each v In v ' (implicit .Cells?)
        Debug.Print v.Column,       ' 1, 2, 3, 4
    Next

    ' Amazing for Excel, no need to declare two vars to iterate over a range
    Debug.Print vbCrLf & "Case10 range in range",
    Set rng = Range("A1:D1") '.Cells.Cells add as many as you want
    For Each rng In rng ' (another implicit .Cells here?)
        Debug.Print rng.Column,     ' 1, 2, 3, 4
    Next
End Sub

更新 1

一个有趣的观察,可以帮助理解其中的一些.关于情况 7 和 8:如果我们对正在迭代的集合持有另一个引用,则行为完全改变:

An interesting observation that can help understanding some of this. Concerning cases 7 and 8: if we hold another reference on the collection being iterated, the behavior changes completely:

    Debug.Print vbCrLf & "Case7 modified",
    Set obj = New Collection: For i = 1 To 4: obj.Add Cells(i, i): Next
    Dim obj2: set obj2 = obj  ' <-- This changes the whole thing !!!
    For Each obj In obj
        Debug.Print obj.Column,    ' 1, 2, 3, 4 Now !!!
    Next

这意味着在最初的情况 7 中,在变量 obj 被分配给集合的第一个元素之后,正在迭代的集合被垃圾收集(由于引用计数).但这仍然很奇怪.编译器应该在被迭代的对象上持有一些隐藏的引用!?将此与正在迭代的数组锁定"的情况 6 进行比较...

This means that in the initial case7 the collection being iterated was garbage-collected (due to reference counting) just after the variable obj was assigned to the first element of the collection. But this is still weird though. The compiler should have held some hidden reference on the object being iterated!? Compare this to case 6 where the array being iterated was "locked"...

更新 2

MSDN 定义的 For 语句的语义可以在 在此页面上.您可以看到它明确规定 end-value 应该只评估一次,并且在循环执行之前.我们应该将这种奇怪的行为视为编译器错误吗?

The semantics of the For statement as defined by MSDN can be found on this page. You can see that it is explicitly stated that the end-value should be evaluated only once and before the execution of the loop proceeds. Should we consider this odd behavior as a compiler bug?

更新 3

又是有趣的案例 7.case7 的反直觉行为不限于变量自身的(说不寻常的)迭代.这可能发生在看似无辜"的代码中,错误地删除了正在迭代的集合上的唯一引用,从而导致其垃圾收集.

The intriguing case 7 again. The counter-intuitive behavior of case7 is not restricted to the (say unusual) iteration of a variable on itself. It may happen in a seemingly "innocent" code that, by mistake removes the only reference on the collection being iterated, leading to its garbage collection.

Debug.Print vbCrLf & "Case7 Innocent"
Dim col As New Collection, member As Object, i As Long
For i = 1 To 4: col.Add Cells(i, i): Next
Dim someCondition As Boolean ' say some business rule that says change the col
For Each member In col
    someCondition = True
    If someCondition Then Set col = Nothing ' or New Collection
    ' now GC has killed the initial collection while being iterated
    ' If you had maintained another reference on it somewhere, the behavior would've been "normal"
    Debug.Print member.Column, ' 1 only
Next

根据直觉,人们期望集合中保留一些隐藏的引用以在迭代期间保持活动状态.不仅没有,而且程序运行流畅,没有运行时错误,可能会导致严重的错误.虽然规范没有规定任何关于在迭代下操作对象的规则,但实现恰好保护和锁定迭代的数组(情况 6)但忽略了 - 甚至不持有虚拟引用 - 在集合上(既不是在字典上,我也测试过).

By intuition one expects that some hidden reference is held on the collection to stay alive during iteration. Not only it doesn't, but the program runs smoothly with no run-time error, leading probably to hard bugs. While the spec does not state any rule about manipulating objects under iteration, the implementation happens to protect and lock iterated Arrays (case 6) but neglects - doesn't even hold a dummy reference - on a collection (neither on a Dictionary, I've tested that also).

关心引用计数是程序员的责任,这不是 VBA/VB6 的精神"和引用计数背后的架构动机.

It's the responsibility of the programmer to care about the reference counting, which is not the "spirit" of VBA/VB6 and the architectural motivations behind reference counting.

更新 4:变体比较诅咒

Variant 在许多情况下表现出奇怪的行为.特别是,比较不同子类型的两个变体会产生不确定的结果.考虑这些简单的例子:

Variants exhibit weird behaviors in many situations. In particular, comparing two Variants of different sub-types yields undefined results. Consider these simple examples:

Sub Test1()
  Dim x, y: x = 30: y = "20"
  Debug.Print x > y               ' False !!
End Sub

Sub Test2()
  Dim x As Long, y: x = 30: y = "20"
  '     ^^^^^^^^
  Debug.Print x > y             ' True
End Sub

Sub Test3()
  Dim x, y As String:  x = 30: y = "20"
  '        ^^^^^^^^^
  Debug.Print x > y             ' True
End Sub

如您所见,当数字和字符串这两个变量都被声明为变体时,比较是未定义的.当其中至少一个被显式输入时,比较成功.

As you can see, when both variables, the number and the string, were declared variants, the comparison is undefined. When at least one of them is explicitly typed, the comparison succeeds.

比较相等时也会发生同样的情况!例如,?2="2" 返回 True,但如果您定义了两个 Variant 变量,为它们分配这些值并进行比较,则比较失败!

The same occurs when comparing for equality! For instance, ?2="2" returns True, but if you define two Variant variables, assign them those values and compare them, the comparison fails!

Sub Test4()
  Debug.Print 2 = "2"           ' True

  Dim x, y:  x = 2:  y = "2"
  Debug.Print x = y             ' False !

End Sub

推荐答案

请查看下方的修改内容!

对于在 Edit2 下也添加的每个编辑

Edit3 上关于 ForEach 和 Collections 的更多编辑

在 Edit4 上对 ForEach 和 Collections 的最后一次编辑

关于 Edit5 中迭代行为的最后说明

当用作循环控制变量或终止条件时,变体评估语义中这种奇怪行为的部分微妙之处.

Part of the subtlety of this odd behavior in the semantics of variant evaluation when used as a loop control variable or terminating condition.

简而言之,当变量是终止值或控制变量时,终止值自然会在每次迭代时由运行时重新评估.但是, 类型,例如 Integer,会直接 推送,因此不会重新计算(并且它的值不会改变)).如果控制变量是 Integer,但终止值是 Variant,则 Variant 被强制为 Integer> 在第一次迭代中,并以类似方式推送.当终止条件是一个涉及 Variant 和一个 Integer 的表达式时,也会出现同样的情况——它被强制转换为一个 Integer.

In a nutshell, when a variant is the terminating value, or the control variable, the terminating value is naturally re-evaluated by the runtime with each iteration. A value type, however, such as an Integer, is pushed directly, and thus not re-evaluated (and its value doesn't change). If the control variable is an Integer, but the terminating value is a Variant, the Variant is coerced to an Integer on the first iteration, and pushed similarly. The same situation arises when the terminating condition is an expression involving a Variant and an Integer - it's coerced to an Integer.

在这个例子中:

Dim v as Variant
v=4
for v= 1 to v
  Debug.print v,
next

变体 v 被赋予一个整数值 1,并且循环终止条件被重新评估,因为终止变量是一个变体 - 运行时识别出 Variant 引用的存在并强制重新计算每次迭代评估.结果,由于循环内重新分配,循环完成.因为变量现在的值为 1,所以满足循环终止条件.

The variant v is assigned an integer value of 1, and the loop termination condition is re-evaluated because terminating variable is a variant - the runtime recognizes the presence of the Variant reference and forces re-evaluation with each iteration. As a result, the loop completes because of the in-loop reassignment. Because the variant now has a value of 1, the loop termination condition is satisfied.

考虑下一个例子:

Dim v as variant
v=4
for v=1 to v-0
   Debug.Print v,
next 

当终止条件是一个表达式时,例如v - 0",该表达式被求值强制常规整数,而不是变体,因此它的硬值在运行时被压入堆栈.因此,不会在每次循环迭代时重新评估该值.

When the terminating condition is an expression, such as "v - 0", the expression is evaluated and coerced to a regular integer, not a variant, and thus its hard value is pushed to the stack at runtime. As a result, the value is not re-evaluated upon each loop iteration.

另一个有趣的例子:

Dim i as Integer
Dim v as variant
v=4
For i = 1 to v
   v=i-1
   Debug.print i,
next

行为如此,因为控制变量是一个整数,因此终止变量也被强制为一个整数,然后压入堆栈进行迭代.

behaves as it does because the control variable is an Integer, and thus the terminating variable is coerced to an integer as well, then pushed to the stack for iteration.

我不能发誓这些是语义,但我相信终止条件或值被简单地压入堆栈,因此整数 被推送,或者 Variant 的 对象引用 被推送,从而在编译器意识到一个变体持有终止值时触发重新评估.当变量在循环中被重新分配,并且在循环完成时重新查询该值,则返回新值,循环终止.

I cannot swear these are the semantics, but I believe the terminating condition or value is simply pushed onto a stack, thus the integer value is pushed, or the Variant's object reference is pushed, thus triggering the re-evaluation when the compiler realizes a variant holds the terminating value. When the variant gets reassigned within the loop, and the value is re-queried as the loop completes, the new value is returned, and the loop terminates.

对不起,如果这有点混乱,但有点晚了,但我看到了这个,忍不住想回答一下.希望它有点道理.啊,不错的 VBA :)

Sorry if that's a little muddy, but it's kinda late, but I saw this and couldn't help but take a shot at an answer. Hope it makes some sense. Ah, good ol' VBA :)

从 MS 的 VBA 语言规范中找到了一些实际信息:

Found some actual info from the VBA language spec at MS:

表达式 [start-value]、[end-value] 和 [step-increment] 按顺序计算一次,并在以下任何计算之前进行.如果 [start-value]、[end-value] 和 [step-increment] 的值不是 Let-coercible 为 Double,则会立即引发错误 13(类型不匹配).否则,使用原始的非强制值继续执行以下算法.

The expressions [start-value], [end-value], and [step-increment] are evaluated once, in order, and prior to any of the following computations. If the value of [start-value], [end-value], and [step-increment] are not Let-coercible to Double, error 13 (Type mismatch) is raised immediately. Otherwise, proceed with the following algorithm using the original, uncoerced values.

[for-statement] 的执行过程如下算法:

Execution of the [for-statement] proceeds according to the following algorithm:

  1. 如果[step-increment]的数据值为0或正数,并且 [bound-variable-expression] 的值大于[end-value] 的值,然后执行 [forstatement]立即完成;否则,前进到第 2 步.

  1. If the data value of [step-increment] is zero or a positive number, and the value of [bound-variable-expression] is greater than the value of [end-value], then execution of the [forstatement] immediately completes; otherwise, advance to Step 2.

如果[step-increment]的数据值为负数,并且[bound-variable-expression] 的值小于[end-value],[for-statement] 的执行立即完成;否则,前进到第 3 步.

If the data value of [step-increment] is a negative number, and the value of [bound-variable-expression] is less than the value of [end-value], execution of the [for-statement] immediately completes; otherwise, advance to Step 3.

[语句块]被执行.如果 [nested-for-statement] 是存在,然后执行.最后,价值[bound-variable-expression] 添加到 [step-increment] 的值并让分配回 [bound-variable-expression].然后执行重复步骤 1.

The [statement-block] is executed. If a [nested-for-statement] is present, it is then executed. Finally, the value of [bound-variable-expression] is added to the value of [step-increment] and Let-assigned back to [bound-variable-expression]. Execution then repeats at step 1.

我从中得出的结论是,意图是对终止条件值进行一次且仅一次的评估.如果我们看到更改该值会改变循环从其初始条件开始的行为的证据,那几乎可以肯定是由于可能非正式地称为意外重新评估,因为它是一个变体.如果是无意的,我们可能只能用轶事证据来预测它的行为.

What I gather from this is that the intent is for the terminating condition value to be evaluated once and once only. If we see evidence that changing that value changes the behavior of the loop from its initial condition, it is almost certainly due to what might be termed informally as accidental re-evaluation because it's a variant. If it's unintentional, we can probably only use anecodtal evidence to predict its behavior.

如果运行时评估循环的开始/结束/步长值,并将这些表达式的值"压入堆栈,则 Variant 值会向进程中抛出byref 扳手".如果运行时没有首先识别变体,对其进行评估,并将那个值作为终止条件,那么几乎肯定会出现奇怪的行为(如您所显示的).正如其他人所建议的那样,VBA 在这种情况下究竟如何处理变体将是 pcode 分析的一项重要任务.

If as the runtime evaluates a loop's start/end/step values, and pushes the "value" of those expressions onto the stack, a Variant value throws a "byref wrench" into the process. If the runtime does not first recognize the variant, evaluate it, and push that value as the terminating condition, curious behavior (as you are showing) would almost certainly ensue. Exactly how VBA handles variants in this case would be a great task for pcode analysis, as others have suggested.

FOREACH

VBA 规范再次提供了对 ForEach 循环对集合和数组的评估的洞察:

The VBA spec again provides insight into the evaluation of ForEach loops over collections and arrays:

表达式 [collection] 在任何 >以下计算之前计算一次.

The expression [collection] is evaluated once prior to any of the >following computations.

  1. 如果[collection]的数据值为数组:

  1. If the data value of [collection] is an array:

如果数组没有元素,则执行[for-each-statement]立即完成.

If the array has no elements, then execution of the [for-each-statement] immediately completes.

如果数组的声明类型是Object,则[bound-variable-expression] 被赋值给 >array 中的第一个元素.否则,[bound-variable-expression] 被赋值给 > 数组中的第一个元素.

If the declared type of the array is Object, then the [bound-variable-expression] is Set-assigned to the first element in the >array. Otherwise, the [bound-variable-expression] is Let-assigned to the >first element in the array.

在设置了 [bound-variable-expression] 后,会执行 [statement-block] >.如果存在 [nested-for-statement],则会执行它.

After [bound-variable-expression] has been set, the [statement-block] >is executed. If a [nested-for-statement] is present, it is then executed.

一旦 [statement-block] 和,如果存在,[nested-for-statement] > 已经完成执行,[bound-variable-expression] 被赋值给 > 数组中的下一个元素(或 Set- 如果它是 >Object 的数组,则分配).当且仅当数组中没有更多元素时, [for-each-statement] 的 >execution 立即完成.否则,再次执行>[statement-block],如果>present,则执行[nested-forstatement],重复此步骤.

Once the [statement-block] and, if present, the [nested-for-statement] >have completed execution, [bound-variable-expression] is Let-assigned to >the next element in the array (or Set-assigned if it is an array of >Object). If and only if there are no more elements in the array, then >execution of the [for-each-statement] immediately completes. Otherwise, >[statement-block] is executed again, followed by [nested-forstatement] if >present, and this step is repeated.

当[for-each-statement]执行完毕后,>[bound-variable-expression]的值为>array最后一个元素的数据值.

When the [for-each-statement] has finished executing, the value of >[bound-variable-expression] is the data value of the last element of the >array.

如果[collection]的数据值不是数组:

If the data value of [collection] is not an array:

[collection] 的数据值必须是对支持实现定义的枚举接口的 >external 对象的对象引用.[bound-variable-expression] 以>实现->定义的方式被Let-assigned或>Set-assigned给[collection]中的第一个元素.

The data value of [collection] must be an object-reference to an >external object that supports an implementation-defined enumeration >interface. The [bound-variable-expression] is either Let-assigned or >Set-assigned to the first element in [collection] in an >implementation->defined manner.

在设置了 [bound-variable-expression] 后,会执行 [statement-block] >.如果存在 [nested-for-statement],则会执行它.

After [bound-variable-expression] has been set, the [statement-block] >is executed. If a [nested-for-statement] is present, it is then executed.

一旦 [statement-block] 和(如果存在)[nested-for-statement] > 已经完成执行,[bound-variable-expression] 被 Set-assigned 到 [collection] 中的下一个元素实现定义的方式.如果 > [collection] 中没有更多元素,则 [for-each->statement] 的执行会立即完成.否则,再次执行 [statement-block],然后是 [nested-for-statement](如果存在),并重复此 >step.

Once the [statement-block] and, if present, the [nested-for-statement] >have completed execution, [bound-variable-expression] is Set-assigned to >the next element in [collection] in an implementation-defined manner. If >there are no more elements in [collection], then execution of the [for-each->statement] immediately completes. Otherwise, [statement-block] is >executed again, followed by [nested-for-statement] if present, and this >step is repeated.

当[for-each-statement]执行完毕后,>[bound-variable-expression]的值为>[collection]中最后一个元素的数据值.

When the [for-each-statement] has finished executing, the value of >[bound-variable-expression] is the data value of the last element in >[collection].

以此为基础,我认为很明显,分配给变量的 Variant 然后成为绑定变量表达式会在此示例中生成数组已锁定"错误:

Using this as a base, I think it becomes clear that a Variant assigned to a variable that then becomes the bound-variable-expression generates the "Array is locked" error in this example:

    Dim v As Variant, vv As Variant
v = Array(1, 1, 1, 1)
i = 1
' Any of the Commented lines below generates the same RT error:
For Each v In v  ' "This array is fixed or temporarily locked"
'For Each vv In v
    'v = 4
    'ReDim Preserve v(LBound(v) To UBound(v))
    If i < UBound(v) Then v(i + 1) = i + 1 ' so we can alter the entries in the array, but not the array itself
    i = i + 1
     Debug.Print vv,            ' 1, 2, 3, 4
Next

使用 'v' 作为 [bound-variable-expression] 会创建一个返回给 V 的 Let 赋值,该赋值被运行时阻止,因为它是正在进行的枚举的目标,以支持 ForEach 循环本身;也就是说,运行时会锁定变体,从而阻止循环将不同的值分配给变体,这是必然发生的.

Using 'v' as the [bound-variable-expression] creates a Let-assignment back to V that is prevented by the runtime because it is the target of an enumeration underway to support the ForEach loop itself; that is, the runtime locks the variant, thus precluding the loop from assigning a different value to the variant as would necessarily have to occur.

这也适用于Redim Preserve" - 调整数组大小或更改数组,从而更改变体的分配,将违反循环初始化时放置在枚举目标上的锁定.

This also applies to the 'Redim Preserve' - resizing or changing the array, thus changing the variant's assignment, is going to violate the lock placed on the enumeration target at the loop's initialization.

关于基于范围的赋值/迭代,请注意非对象元素的单独语义;外部对象"提供了一个特定于实现的枚举行为.一个 excel Range 对象有一个 _Default 属性,当只被对象名称引用时会被调用,在这种情况下,当用作ForEach 的迭代目标(因此不会产生锁定错误,因为它具有与 Variant 不同的语义):

With regard to Range-based assignments/iteration, note the separate semantics for non-object elements kicks in; the "external objects" provide an implementation-specific enumeration behavior. An excel Range object has a _Default property that is being called when referenced by the object name only, as in this case, which does not take an implicit lock when used as the iteration target of the ForEach (and thus does not generate the locking error, as it has different semantics than the Variant variety):

Debug.Print vbCrLf & "Case10 range in range",
Set rng = Range("A1:D1") '.Cells.Cells add as many as you want
For Each rng In rng ' (another implicit .Cells here?)
    Debug.Print rng.Column,     ' 1, 2, 3, 4
Next

(_Default 属性可以通过在 VBA 对象浏览器中通过突出显示 Range 对象、右键单击并选择显示隐藏成员"来检查 VBA 对象浏览器中的 Excel 对象库来识别).

(The _Default property can be identified by examining the Excel object library within the VBA Object Browser via highlighting the Range object ,right-clicking, and selecting "Show Hidden Members").

集合

涉及集合的代码变得有趣而且有点麻烦:)

The code involving collections gets interesting and a little hairy :)

Debug.Print vbCrLf & "Case7 obj in col",
Set obj = New Collection: For i = 1 To 4: obj.Add Cells(i, i): Next
For Each obj In obj
    Debug.Print obj.Column,    ' 1 only ?
Next

Debug.Print vbCrLf & "Case8 var in col",
Set v = New Collection: For i = 1 To 4: v.Add Cells(i, i): Next
For Each v In v
    Debug.Print v.column,      ' nothing!
Next

这里只需要考虑真正的错误.当我第一次在 VBA 调试器中运行这两个示例时,它们完全按照初始问题中提供的 OP 运行.然后,在经过几次测试后重新启动例程后,然后将代码恢复到其原始形式(如此处所示),后者的行为任意开始与其上方基于 object 的前辈的行为匹配!只有在我停止 Excel 并重新启动它之后,后一个循环的 原始 行为(不打印任何内容)才返回.除了编译器错误之外,真的没有办法解释这一点.

This is where nothing more than a genuine bug has to be considered at play. When I first ran these two samples in the VBA debugger, they ran precisely as the OP offered in the initial question. Then, after a restart of the routine following a few tests, but then restoring the code to its original form (as shown here), the latter behavior arbitrarily started matching that of the object-based predecessor above it! Only after I stopped Excel, and restarted it, did the original behavior of the latter loop (printing nothing), return. There's really no way to explain that other than a compiler bug.

EDIT4 具有变体的可重现行为

在注意到我在调试器中做了某事以强制基于变体的迭代通过集合至少循环一次(就像对象版本一样),我终于找到了改变行为的代码可重现方式

After noting that I'd done something within the debugger to force the variant-based iteration through a Collection to loop at least once (as it had with the Object version), I finally found a code-reproducible way of changing the behavior

考虑这个原始代码:

Dim v As Variant, vv As Variant

Set v = New Collection: For x = 1 To 4: v.Add Cells(x, x): Next x
'Set vv = v
For Each v In v
   Debug.Print v.Column
Next

这本质上是 OP 的原始情况,ForEach 循环在没有一次迭代的情况下终止.现在,取消注释 'Set vv=v' 行,然后重新运行:现在 For Each 将迭代一次.我认为毫无疑问我们在 VB 运行时的 Variant 评估机制中发现了一些非常(非常!)微妙的错误;另一个变体"的任意设置等于循环变量会强制进行在 For Each 评估中不会发生的评估 - 我怀疑这与 Collection 在 Variant 中表示为 Variant/Object/Collection 的事实有关.添加这个虚假的集合"似乎会迫使这个问题并使循环像基于对象的版本那样运行.

This is essentially the OP's original case, and the ForEach loop terminates without a single iteration. Now, uncomment the 'Set vv=v' line, and re-run: now the For Each will iterate one time. I think there's no question that we've found some very (very!) subtle bug in Variant evaluation mechanism in the VB runtime; the arbitrary setting of another 'Variant' equal to the loop variable forces an evaluation that does not take place in the For Each evaluation - and I suspect that's tied to the fact that the Collection is represented within the Variant as a Variant/Object/Collection. Adding this bogus 'set' seems to force the issue and make the loop operate as the Object-based version does.

关于迭代和集合的最终想法

这可能是我对这个答案的最后一次编辑,但有一件事我不得不强迫自己确保在观察奇怪的循环行为期间,当变量被用作绑定变量表达式"和限制表达式是,特别是当涉及到变体"时,有时由于迭代改变边界变量表达式"的内容而导致行为.也就是说,如果您有:

This will probably be my last edit to this answer, but one thing I had to force myself to be sure I recognized during the observation of odd loop behavior when a variables was used as the 'bound-variable-expression' and the limit expression was that, particularly when it comes to 'Variants', sometimes the behavior is induced by virtue of the iteration changing the contents of the 'bound-variable-expresssion.' That is, if you have:

Dim v as Variant
Dim vv as Variant
Set v = new Collection(): for x = 1 to 4: v.Add Cells(x,x):next
Set vv = v ' placeholder to make the loop "kinda" work
for each v in v
   'do something
Next

重要的是要记住(至少对我来说是这样)要记住,在 For Each 中,'v' 中包含的绑定变量表达式"被改变的迭代.也就是说,当我们开始循环时,v 持有一个 Collection,枚举就开始了.但是当枚举开始时, v 的内容现在是 枚举 的产物——在这种情况下,是一个 Range 对象(来自 Cell).这种行为可以在调试器中看到,因为您可以观察到 'v' 从 Collection 到 Range;这意味着迭代中的下一次踢将返回 Range 对象的枚举上下文将提供的任何内容,而不是集合".

it is vital to remember (at least it was for me) to keep in mind that within the For Each, the 'bound-variable-expression' held in 'v' gets changed by virtue of the iteration. That is, when we start the loop, v holds a Collection, and the enumeration begins. But when that enumeration starts, the contents of v are now the product of the enumeration - in this case, a Range object (from the Cell). This behavior can be seen in the debugger, as you can observe 'v' go from Collection to Range; meaning that the next kick in the iteration returns whatever the enumeration context of the Range object would provide, not the 'Collection.'

这是一项很棒的研究,我感谢您的反馈.它帮助我比我想象的更好地理解事物.除非对此有更多评论或问题,否则我怀疑这将是我对答案的最后一次编辑.

This has been a great study and I appreciate the feedback. It's helped me understand things even better than I thought. Unless there are more comments or questions on this, I suspect this will be my last edit to the answer.

这篇关于变量自身迭代 - 不同类型的不同行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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