Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reproducing the "close over the variable of a foreach" gotcha

Using Visual Studio 2013, I'm trying to reproduce the gotcha mentioned in Eric Lippert's blog post "Closing over the loop variable considered harmful".

In the project properties, I selected "C# 3.0" as the language version (Build > Advanced…). Further, I selected ".NET Framework 3.5" as the target framework, as though I think this shouldn't be necessary as this solely concerns the language.

Running his code:

using System;
using System.Collections.Generic;

namespace Project1
{
    class Class1
    {
        public static void Main(string[] args)
        {
            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());
        }
    }
}

Expected output:

120
120
120

Actual output:

100
110
120

As answered by Eric Lippert himself in "Is there a reason for C#'s reuse of the variable in a foreach?":

The for loop will not be changed, and the change will not be "back ported" to previous versions of C#. You should therefore continue to be careful when using this idiom.

What am I doing wrong?

like image 768
xehpuk Avatar asked Dec 16 '15 17:12

xehpuk


2 Answers

Scott's answer is correct, but could use some additional clarification.

The problem here is that the "language version" switch doesn't do what you think it does. This is in my opinion a bit of a misfeature, since it is quite misleading. The "language version" switch does not mean "use the old compiler"; it is not a compatibility mode.

Rather, it means "use the current compiler, and produce an error if I use a feature that was not available in the selected version of the language."

The reason for this switch is so that one person on a dev team can "try out" a new version of the compiler to make sure that their code still works, but know before they check in that they have not accidentally used a language feature that their teammates' compilers will choke on. So if you set the language version to 3.0 then "dynamic" will not work, (because it was added in C# 4.0) but it is still whatever version of the compiler you have installed.

As Scott points out, if you want to use the old compiler you'll have to actually find a copy of the old compiler on your machine somewhere and use it explicitly.

See http://ericlippert.com/2013/04/04/what-does-the-langversion-switch-do/ for some more examples of what this switch does and does not do.

like image 86
Eric Lippert Avatar answered Oct 04 '22 01:10

Eric Lippert


I believe that even if you choose C# 3.0 for your language the compiler still does the new behavior, I don't think you can recreate the old behavior in the new compiler. The only thing setting the language does is restrict you from using language features that where introduced in the later version of the language.

You must find a copy of the old compiler (VS 2012 or older) to get the behavior. When Eric said "the change will not be "back ported" to previous versions of C#" he meant they will not be releasing an update for VS2012 to change the behavior in that compiler (When the post you quoted was written VS2013 had just came out and VS2012 was still in widespread use).

EDIT: If you really want to re-create the behavior you need to write your own manual for-each loop.

public static void Main(string[] args)
{
    var values = new List<int>() { 100, 110, 120 };
    var funcs = new List<Func<int>>();

    using (var enumerator = values.GetEnumerator())
    {
        int v;
        while (enumerator.MoveNext())
        {
            //int v; // In C# 5+ the variable is declared here.
            v = enumerator.Current;
            funcs.Add(() => v);
        }
    }

    foreach (var f in funcs)
        Console.WriteLine(f());
}

This will output your expected output.

like image 37
Scott Chamberlain Avatar answered Oct 03 '22 23:10

Scott Chamberlain