tags:

views:

96

answers:

3

Consider the following code:

public class MyClass
{
   public delegate string PrintHelloType(string greeting);


    public void Execute()
    {

        Type[] types = new Type[] { typeof(string), typeof(float), typeof(int)};
        List<PrintHelloType> helloMethods = new List<PrintHelloType>();

        foreach (var type in types)
        {
            var sayHello = 
                new PrintHelloType(greeting => SayGreetingToType(type, greeting));
            helloMethods.Add(sayHello);
        }

        foreach (var helloMethod in helloMethods)
        {
            Console.WriteLine(helloMethod("Hi"));
        }

    }

    public string SayGreetingToType(Type type, string greetingText)
    {
        return greetingText + " " + type.Name;
    }

...

}

After calling myClass.Execute(), the code prints the following unexpected response:

Hi Int32
Hi Int32
Hi Int32  

Obviously, I would expect Hi String, Hi Single, Hi Int32, but apparently it is not the case. Why the last element of the iterated array is being used in all the 3 methods instead of the appropriate one?

How would you rewrite the code to achieve the desired goal?

+12  A: 

Welcome to the world of closures and captured variables :)

Eric Lippert has an in-depth explanation of this behaviour:

basically, it's the loop variable that is captured, not it's value. To get what you think you should get, do this:

foreach (var type in types)
{
   var newType = type;
   var sayHello = 
            new PrintHelloType(greeting => SayGreetingToType(newType, greeting));
   helloMethods.Add(sayHello);
}
SWeko
The @Eric Lippert beacon has been lit.
Anthony Pegram
There is no god but Anders, and Eric is his prophet :)
SWeko
+2  A: 

You can fix it by introducing additional variable:

...
foreach (var type in types)
        {
            var t = type;
            var sayHello = new PrintHelloType(greeting => SayGreetingToType(t, greeting));
            helloMethods.Add(sayHello);
        }
....
STO
+1  A: 

As a brief explanation that alludes to the blog postings that SWeko referenced, a lambda is capturing the variable, not the value. In a foreach loop, the variable is not unique on each iteration, the same variable is used for the duration of the loop (this is more obvious when you see the expansion the compiler performs on the foreach at compile time). As a result, you've captured the same variable during each iteration, and the variable (as of the last iteration) refers to the last element of your set.

Anthony Pegram