1

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?

Andreas
  • 1,997
  • 21
  • 35

3 Answers3

5

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.

Community
  • 1
  • 1
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
1
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.

nvoigt
  • 75,013
  • 26
  • 93
  • 142
0

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;
}
Sourav 'Abhi' Mitra
  • 2,390
  • 16
  • 15