Friday, September 12, 2008

Are C# foreach iteration variables immutable?

Are C# foreach iteration variables immutable?
Well, they are supposed to be and sure enough this code won't compile:
var words = new List { "monkey", "nut", "mice" };
foreach (string word in words)
{
  word = "sugar";
}

The C# compiler reports: Cannot assign to 'word' because it is a 'foreach iteration variable'.
But now consider the following code:
var words = new List { "monkey", "nut", "mice" };
List printers = new List();
foreach (var word in words)
{
  printers.Add(
    new Action(() => Console.WriteLine(word)));
}
printers.ForEach(printer => printer());

To my surprise this generates:
mice
mice
mice

This may be conform the language specifications, but to me it is highly unlogical, as it violates the illusion that word is immutable with scope local to a single iteration.
Obviously there are always work-arounds to get 3 different words, like this one (to rub it in):
var words = new List { "monkey", "nut", "mice" };
List printers = new List();
foreach (var word in words)
{
  string local=word;
  printers.Add(
    new Action(() => Console.WriteLine(local)));
}
printers.ForEach(printer => printer());

Or this one (more usable):
var words = new List { "monkey", "nut", "mice" };
var printers = new List(
  words.Select(word =>
    new Action(() => Console.WriteLine(word))));
printers.ForEach(printer => printer());

I have been checking out F# lately. It's really neat, I'll have more to say about it subsequent posts. But of course I wanted to be sure the situation in F# is more logical:
let printers = new ResizeArray<(unit->unit)>()
for word in [ "monkey"; "nut"; "mice" ] do
  (fun ()->printfn "%s" word) |> printers.Add
Seq.iter (fun action->action()) printers

No fooling around it gets us to the expected:
monkey
nut
mice

Maybe there is some thruth after all in the claim that functional programming makes your code more reliable :-)

2 comments:

Unknown said...

"This may be conform the language specifications, but to me it is highly unlogical, as it violates the illusion that word is immutable with scope local to a single iteration."

This isn't just about conforming to the language specification. This is just how lexical closures are supposed to work. A closure binds to the variable -- not the value. So, when you print the variable later, it prints the very last value of that variable.

Check out the lively discussion at the end of http://blogs.msdn.com/abhinaba/archive/2005/10/18/482180.aspx.

Anonymous said...

I agree completely on the closures. My point is about the scope of the iteration variable.

foreach (var word in words)
{
...
}

translates to

{
var e = words.GetEnumerator();
string word;
while (e.MoveNext()) {
word = e.Current;
...
}
}

The immutability of the iteration variable would make the following translation much more logical IMHO:

{
var e = words.GetEnumerator();
while (e.MoveNext()) {
var word = e.Current;
...
}
}