Req1: Put the loop control variable inside the loop


[This post is part of a series, “wish-list for future versions of VB“]


 


IDEA: In a “For Each” loop, put the loop control variable’s scope inside the loop. Look at the following code:


        Dim lambdas(10) As Func(Of Integer, Integer)


        For x = 1 To 10


            lambdas(x) = Function(i) i + x


        Next


 


        Dim add5 = lambdas(5)(1) ‘ evaluates to 12: should evaluate to 6


You’d expect it to assign “6” to the variable, because the 5th lambda should have been one that adds the number 5 to its argument. But in both VB and C#, the scope of “x” is actually the entire loop, and so every lambda shares the same “x”, and it by the end of the loop it ends up with the value 11, and hence the answer 12.


VB currently warns you about this: “warning BC42324: Using the iteration variable in a lambda expression may have unexpected results.” Too right it has unexpected results! Unfortunately, the compiler has no way in general of knowing whether your use of the iteration variable is safe or not, so you also get the warning in safe code:


        For x = 1 To 10


            Dim y = From i In {1, 2, 3, 4, 5} Where i < x Select i


            Console.WriteLine(y.Count)


        Next


 


VB should put the scope of the variable inside the loop. This will make the code work in the obvious way. It would be a breaking change, of course (e.g. the first sample would print “6” rather than “12”). But every case that it breaks is a case where the VB compiler already gives a warning.


Note that this issue becomes more important as we add more features to the language which use “delayed execution” blocks — things like lamdas, LINQ, async and anonymous iterators will all run into the same issue.


 


Provisional evaluation from VB team: This is a decent idea, one that will rise in importance as we add further delayed-execution constructs to the language.


 

Comments (4)

  1. Joshua Frank says:

    This sounds really useful.  Right now I have dozens of places where I do this:

    For variable in list

     ‘make a copy to avoid the BC42324 warning

     Dim tmp = variable

     ‘use tmp in some lambda

     Dim x = from z in otherlist where z.name = tmp

     Next

    If you can narrow the scope, that would be great!  If this is too hard, or too breaking, I’d suggest a keyword like (but with a better name) CSafeCopy:

    For variable in list

     ‘use the variable directly in some lambda

     Dim x = from z in otherlist where z.name = CSafeCopy(variable)

     Next

    In other words, the compiler would make the needed copy to avoid side effects.

    But your way is much better!

  2. The worst part of this I think is that it would be a very non-intuitive difference between C# and VB (assuming C# doesn’t make the same change).

    It’s a good idea, but I think it’s too late to change current behavior.

  3. Jonathan Allen says:

    Just add another Option flag to deal with compatibility. It’s annoying, but it sure beats our current situation.

    As for C#, they are even worse off then use because they don’t even have a compiler warning.

  4. Kyralessa says:

    Eric Lippert wrote about this issue in depth, in several posts.  Changing it would be a breaking change.  And yet I’m in favor of changing it because the current behavior is so counterintuitive.

    In real life, we’d always declare the variable inside the loop and regard it as part of the loop.  If we did an enumerator the long way (with Current and MoveNext()), we wouldn’t put any of the variables outside the loop.

    All the current behavior does is lead to bugs; there’s no case where you’d *want* it to do what it currently does.