
*Moved to: http://fluentbytes.com/changed-behavior-outervariables-in-c/
Last month I did one of my talks called Introduction to the LINQ programming model at the VSLive! conference in Orlando FL. While on stage I was all of a sudden caught of guard when I showed a common problem C# programmers run into when working with LINQ.
The example I always show is the following loop where we have a lambda expression capturing the loop variable
char[] vowels = { 'a', 'e', 'o', 'i', 'u' }; query = "not what you might expect"; foreach (var vowel in vowels) { query = query.Where(character => character != vowel); } printResults(query);
Here most people expect the result of the printed string to be:
“nt wht y mght xpct” but it will print “not what yo might expect”
The logical explanation for this is that the lambda expression captures the reference to the variable location vowel and that location contains the last value of the loop when you print the results after the loop.
I have demonstrated this many many times, but all of a sudden the string printed:
“nt wht y mght xpct”
I took me some time to figure out what has caused my demo to all of a sudden change in behavior, but the explanation is quite simple. This was the first time I used Visual Studio 2012 for this demo and my project was converted to target .NET 4.5.
Apparently Microsoft made the decision that they wanted to fix this common misconception and make the compiler more smart about the fact that the for loop introduces only a local variable to the loop and it can be considered as a local variable and therefore capture the value in stead of the reference!
I don’t expect this to cause a lot of trouble in existing programs. I can’t think of any reason why one would like to make useful use of the previous behavior. You do need to be aware of the fact that this change is only made for foreach loops. So if you program a for loop you will still get the previous behavior where the lambda expression captures the outervariable of the loop.
Consider the following code:
var values = new List<int>() { 100, 110, 120 }; var funcs = new List<Func<int>>(); foreach (var v in values) funcs.Add(() => v); foreach (var f in funcs) Console.WriteLine(f());
This will yield different results in .NET 4.5 then in previous versions.
Previous versions will yield the result 120, 120, 120 and 4.5 and above will yield 100, 110, 120
Now consider the following code:
funcs = new List<Func<int>>(); for (var v=100; v < 120; v+=10) funcs.Add(() => v); foreach (var f in funcs) Console.WriteLine(f());
What will the result of this code be when compiled targeting .NET 4.5?
Yep, it will result in 120, 120, 120
Microsoft has made the decision that with the for loop the variable of the loop is influenced by the += operator and therefore is obviously not local to the inner loop and therefore the behavior remains the same for the future.
You can find more details on this on Erik Lipperts blog over here.
Hope this shines some light on the reason of my demo failing at VS Live :-),
Marcel
Follow my new blog on http://fluentbytes.com