Loop Closures in Java & C#

The lambda expressions introduced in Java 8 can capture, or “close over”, any local variable that’s within scope and effectively final (Java 8 Language Specification §15.27.2, §4.12.4). Interestingly, this includes the loop variables of enhanced for loops, or for-each loops as I prefer to call them. Cay Horstmann mentions this very useful but non-obvious fact on page 119 of his new book Core Java for the Impatient.

Why is the loop variable of a for-each loop effectively final? Doesn’t it get updated in each iteration, just like its equivalent in an ordinary for loop? No, because the Java 8 Language Specification explicitly requires (§14.14.2) that the for-each loop variable is declared anew within the loop body for every single iteration, and only once assigned a value: that of the current array element or iterator result. Therefore the loop variable is effectively final in each iteration.

This has two important consequences. The first is that yes, you can directly use the for-each loop variable in any lambda expression you care to define within the loop. The second is that this variable will actually provide the value you would expect it to provide, namely the value during the iteration when the lambda expression was being evaluated for later execution. If you’re a C# programmer you will immediately understand why this is noteworthy. Otherwise, read on to learn why being unable to close over mutable variables (including for loop variables) is actually a good thing…

Java Loop Closures

First, a very simple example to illustrate what we’re talking about here. The following program stores three Runnable lambdas for later execution. Each captures one of the strings in an array that our for-each loop iterates over. The output is exactly what you’d expect: a b c.

import java.util.ArrayList;

public class ClosureTest {

    public static void main(String[] args) {
        final String[] items = new String[] { "a", "b", "c" };
        final ArrayList<Runnable> lambdas = new ArrayList<>();

        for (String item: items)
            lambdas.add(() -> System.out.println(item));

        lambdas.forEach(Runnable::run);
    }
}

C# Loop Closures

The current C# version, C# 5 shipping with .NET 4.5, defines the foreach loop variable in the same fashion as Java 8 and produces identical lambda capturing behavior. However, that was not always the case. Prior to C# 5, foreach loop variables were living and mutable through all loop iterations, just like ordinary for loop variables.

This resulted in the vexing behavior Eric Lippert describes in Closing over the loop variable considered harmful. When lambdas were defined during a loop but invoked at a later time, they would all see the final value of the loop variable, rather than the current one during the iteration when they were evaluated! The only way to avoid this was to manually copy the current iteration value into a newly defined variable, and pass that variable to the lambda expression.

If you’re running Windows you can easily try this out for yourself, without having to install Visual Studio or other tools. Just save the following test program to file ClosureTest.cs or the like, then compile it using either of the two compiler versions listed below:

using System;
using System.Collections.Generic;

public static class ClosureTest {

    public static void Main() {
        var items = new String[] { "a", "b", "c" };
        var lambdas = new List<Action>();

        foreach (String item in items)
            lambdas.Add(() => Console.WriteLine(item));

        foreach (Action lambda in lambdas)
            lambda();
    }
}
  • Compiled with C:\Windows\Microsoft.NET\Framework64\<b>v4.0.30319</b>\csc.exe, the output is a b c.
  • Compiled with C:\Windows\Microsoft.NET\Framework64\<b>v3.5</b>\csc.exe, the output is c c c!

Note that the v4.0.30319 directory actually contains .NET 4.5 with C# 5 on my system. This was an in-place upgrade for .NET 4.0 and so reuses the same directory structure, including the old version name. Interestingly, specifying the /langversion switch on the new compiler in order to emulate C# 3/4 does not change the foreach capturing behavior. That strikes me as a bug in the implementation of langversion but I suppose there’s little demand for a behavior that nobody wanted in the first place…

You’ll probably agree that the foreach capturing of older C# versions is extremely counter-intuitive, but note that it still necessarily persists in for loops. Java avoids the possibility of such behavior entirely by requiring that all captured local variables are effectively final. While sometimes restrictive, it’s easy enough to work around when you do need a mutable value (just use a class-scoped variable) and saves thousands of programmers from nerve-wracking debugging sessions.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.