Question

Is there any elegant way to "freeze" the variables used in an action that is returned from a method?

Just have a look at the following code:

static void Main(String[] args) 
{
    foreach(Action a in ClosureTrap("a", "b")) 
    {
        a();
    }
}

static List<Action> ClosureTrap(params String[] strings) 
{
    List<Action> result = new List<Action>();
    foreach(String s in strings) 
    {
        result.Add(() => Console.WriteLine(s));
    }
    return result;
}

This code will write two lines to the console, both containing "b". The reason for this is not too hard to find: The last value for "s" in ClosureTrap is "b".

Is there any elegant way to get two lines "a" and "b" as output on the console?

Currently I am using an additional method to create the delegate. But by doing this, the closures loose a lot of their elegance:

static List<Action> ClosureTrap(params String[] strings) 
{
    List<Action> result = new List<Action>();
    foreach(String s in strings) 
    {
        result.Add(Freeze(s));
    }
    return result;
}

static Action Freeze(String s) 
{
    return () => Console.WriteLine(s);
}

Is there a better way to do this?

Was it helpful?

Solution

There's no general way to do this - but if you're only bothered by the specific issue of foreach, then there are two options:

  • Start using a C# 5 compiler (you don't need to target .NET 4.5 or anything like that). The rules around captured foreach iteration variables changed (to sane ones) for C# 5.
  • If you're stuck with C# 3 or 4, capture a local variable declared in the loop:

    foreach(String s in strings) 
    {
        string copy = s;
        result.Add(() => Console.WriteLine(copy));
    } 
    

    That way each iteration of the loop captures a separate variable, which never changes value.

OTHER TIPS

foreach(String s in strings) 
{
    var localCopy = s; // <= local copy
    result.Add(() => Console.WriteLine(localCopy));
}

You need to make a local copy of your variable.

Well this seems to work:

static List<Action> ClosureTrap(params String[] strings)
{
    List<Action> result = new List<Action>();
    strings.ToList().ForEach(s => result.Add(() => Console.WriteLine(s)));
    return result;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top