1

I don't know how to explain the problem exactly in word. I have written the code to reproduce the problem, and I have tried my best to make the code easy to read.

delegate string del();
static void Main(string[] args)
{

    string[] ss = { "a", "b" };
    Dictionary<string, del> dic = new Dictionary<string, delSS>();
    foreach (string s in ss) {
        dic[s] = () => s;
    }
    foreach (string s in ss) {
        System.Console.WriteLine("{0}:{1}", s, dic[s]());
    }
    System.Console.ReadLine();
}

I created two lambda expression for "a" and "b", for "a", it returns "a", and for "b", it returns "b". The delegates are saved in a dictionary named dic. And I call them by dics. The expected result is

a:a
b:b

and this is the result in VS2013. But in VS2008, the result is

a:b
b:b

I have tried the versions of .Net Framework from 3.0 to 4.5 in VS2013, and all of them gives the same result. I also tried the 3.0 and 3.5 in VS2008, and they gives same results too. I thought it means it's not a bug of the Framework, but bug of Compiler. I'm developing an application for WinCE 6.0, which is only supported in VS2008 and earlier versions. That's why I have to use VS2008. I found the problem when I'm debugging the WinCE application. I thought it's a bug of Compact Framework, but it's proved to be a bug of VS2008. Does anyone know how to fix it?

iuradz
  • 1,128
  • 1
  • 12
  • 25

3 Answers3

1

Well the compiler got better I give it that ;)

Try

   foreach (string s in ss) {
        var closed = s;
        dic[s] = () => closed;
    }

This will create a new variable/value for each s so that the closure will not point to your s that will be mutated by the foreach loop till the end.

Random Dev
  • 51,810
  • 9
  • 92
  • 119
1

Capturing a loop variable inside a closure is tricky. It's nice that VS2013 sorts it out for you, but I don't think it's safe to use at all.

Jon Skeet explains it nicely here (and in his books): Captured variable in a loop in C# . Code quality tools like Resharper issue a warning anytime you try to do this.

Community
  • 1
  • 1
Paul-Jan
  • 16,746
  • 1
  • 63
  • 95
0

Here is a solution. I have made the following modification and i tried it in vs2008, it works perfectly delegate string del(string sr);

    static void Main( )
    {

        string[] ss = { "a", "b" };
        Dictionary<string, del> dic = new Dictionary<string, del >();
        foreach (string s in ss)
        {
            dic[s] = (sr) => sr;
        }
        foreach (string s in ss)
        {
            System.Console.WriteLine("{0}:{1}", s, dic[s].Invoke(s));
        }
        System.Console.ReadLine();
    }
  1. The del delegate now has parameter sr
  2. You need to invoke the delegate at the point of use, and pass in the variable 's'

The fact that you are using a delegate implies that you should make use of the advantage of the fact that you can pass parameter into the delegate. As you places a variable into the lambda function, it makes the CLR choose the last position of the foreach loop pointer as the s variable for all.

Lolu Omosewo
  • 263
  • 1
  • 4