33 votes

Variable itérant sur elle-même - comportement différent selon les types

Veuillez consulter les dernières mises à jour à la fin du post.

En Particulier, voir la mise à Jour 4: la Variante de comparaison de la Malédiction


J'ai déjà vu des camarades de se frapper la tête contre le mur, afin de comprendre comment une variante fonctionne, mais n'aurait jamais imaginé que je vais avoir mon propre mauvais moment avec elle.

J'ai utilisé avec succès le VBA suivant la construction:

For i = 1 to i

Cela fonctionne parfaitement lors de la i est un nombre Entier ou tout type numérique, le parcours de 1 à la valeur d'origine de l' i. Je le fais dans les occasions où i est ByVal paramètre - pourrait-on dire paresseux pour m'épargner la déclaration d'une nouvelle variable.

Ensuite, j'ai eu un bug lors de cette construction "arrêté" fonctionne comme prévu. Après quelques dures de débogage, j'ai trouvé qu'il ne fonctionne pas de la même façon lorsque l' i n'est pas déclaré explicitement comme type numérique, mais un Variant. La question est double:

1 - Quelles sont la sémantique exacte de l' For et de la For Each boucles? Je veux dire, qu'est-ce que la séquence d'actions que le compilateur s'engage, et dans quel ordre? Par exemple, l'évaluation de la limite de précéder l'initialisation du compteur? Est-ce la limite copié et "fixe" quelque part avant la boucle commence? Etc. La même question s'applique à l' For Each.

2 - Comment expliquer les différents résultats sur les variantes et explicite les types numériques? Certains disent une variante est un (immuable) type de référence, cette définition expliquer le comportement observé?

J'ai préparé un MCVE pour les différents (indépendant) les scénarios impliquant l' For et de la For Each des déclarations, combiné avec des entiers, des variantes et des objets. Les résultats surprenants envie de définir sans ambiguïté la sémantique ou, pour le moins, vérifier si ces résultats sont conformes à la définition de la sémantique.

Toutes les idées sont les bienvenues, y compris partielle, ceux qui expliquent certains des surprenants résultats ou leurs contradictions.

Merci.

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

Mise à JOUR 1

Une observation intéressante qui peut aider à la compréhension de certains des ce. Concernant les cas 7 et 8: si nous avons une autre référence sur la collection itérée, le comportement change du tout au tout:

    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

Cela signifie que, dans la première case7 la collection itérée est le garbage collector (en raison de comptage de référence) juste après la variable obj a été affecté au premier élément de la collection. Mais c'est quand même bizarre si. Le compilateur doit avoir lieu cachées référence sur l'objet itérée!? Comparer ce cas 6 où le tableau itérée a été "verrouillé"...

Mise à JOUR 2

La sémantique de l' For énoncé tel que défini par le MSDN peuvent être trouvés sur cette page. Vous pouvez voir qu'il est explicitement indiqué que l' end-value devraient être évaluées qu'une seule fois et avant l'exécution de la boucle produit. Devrions-nous considérer ce comportement étrange, comme un compilateur bug?

Mise à JOUR 3

Le cas intrigant 7 nouveau. La contre-intuitive du comportement de case7 n'est pas restreint à l' (disons plutôt rare) itération d'une variable sur lui-même. Il peut arriver dans une apparence de "innocent" du code qui, par erreur supprime la seule référence sur la collection itérée, conduisant à un son de collecte des ordures.

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

Par intuition, on s'attend à ce que certains secrets de référence est tenu sur la collecte de rester en vie au cours d'une itération. Non seulement il ne l'a pas, mais le programme fonctionne bien avec pas d'erreur d'exécution, conduisant probablement à dur de bugs. Alors que la spécification ne fait pas état d'une quelconque règle sur la manipulation d'objets en vertu de l'itération, la mise en œuvre qui se passe pour protéger et verrouiller itéré Tableaux (cas 6), mais néglige - n'a même pas tenir un mannequin de référence sur un ensemble (ni sur un Dictionnaire, j'ai testé aussi).

Il est de la responsabilité du programmeur de soins sur le comptage de référence, ce qui n'est pas "l'esprit" de VBA/VB6 et l'architecture des motivations de comptage de référence.

Mise à JOUR 4: La Variante de Comparaison de la Malédiction

Variants présentent des comportements bizarres dans de nombreuses situations. En particulier, la comparaison de deux Variantes de différents sous-types, les rendements des résultats indéfinis. Considérez ces exemples simples:

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

Comme vous pouvez le voir, lorsque les deux variables, le nombre et la chaîne, ont été déclarés variantes, la comparaison n'est pas défini. Lorsqu'au moins l'un d'entre eux est explicitement tapé, la comparaison réussit.

La même chose se produit lors de la comparaison pour l'égalité! Par exemple, ?2="2" retourne Vrai, mais si vous définissez deux Variant variables, de leur attribuer ces valeurs et de les comparer entre eux, l'échec de la comparaison de!

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

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

End Sub

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X