8

I executed the following code with C# 3.5 and 4.0. The results are entirely different.

    static void Main()
    {       
        int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        List<IEnumerable<int>> results = new List<IEnumerable<int>>();

        foreach (var num in numbers)
        {
            results.Add(numbers.Where(item => item > num));
        }

        foreach (var r in results)
        {
            Console.WriteLine("{0}", r.Count());
        }        
    }

With Microsoft (R) Visual C# 2008 Compiler version 3.5.30729.5420 the output is 0 0 0 0 0 0 0 0 0 0.

But with Microsoft (R) Visual C# Compiler version 4.0.30319.17929 the output is 9 8 7 6 5 4 3 2 1 0.

I have a faint idea that this is because of deferred execution or lazy evaluation but haven't clearly understood how it is responsible for different outputs here.

Correction: Sorry it was .NET 3.5 and 4.5 and also added compiler versions Please explain.

mssrivatsa
  • 343
  • 1
  • 3
  • 10
  • 2
    I get the same output in 3.5 and 4.0, { 9 8 7 ... } are you sure you're not doing something different? – James Gaunt Sep 05 '13 at 15:11
  • It is giving me same results .... – Habib Sep 05 '13 at 15:11
  • 3
    I believe this change happened with 4.5, not 4.0. – Joe Enos Sep 05 '13 at 15:12
  • 2
    @JoeEnos It has nothing to do with the version of .NET, it is related to the version of C# being used. – Servy Sep 05 '13 at 15:13
  • @Servy Gotcha, thanks. Hard to keep those distinctions clear sometimes. – Joe Enos Sep 05 '13 at 15:14
  • @JoeEnos I made a mistake with .NET version but in the question I mentioned the version of compiler right – mssrivatsa Sep 05 '13 at 15:15
  • @mssrivatsa No you didn't. It was changed in C# 5.0, not 4.0. There also is no such thing as C# 3.5. – Servy Sep 05 '13 at 15:15
  • @Servy No I had the different output with C# 4.0 compiler Microsoft (R) Visual C# Compiler version 4.0.30319.17929 – mssrivatsa Sep 05 '13 at 15:16
  • @mssrivatsa Then you are mistaken. It was changed in C# 5.0. The 4.0 compiler wouldn't produce that result. – Servy Sep 05 '13 at 15:20
  • @Servy: Though there is no *language* C# 3.5, there is a *compiler release* that is version 3.5. The compiler version numbers match the version of the framework they shipped with, not the language they analyze. Yes, this is unnecessarily confusing, and I heartily wish that my former colleagues would get their versioning story straight. This is a perennial problem across Microsoft. – Eric Lippert Sep 05 '13 at 15:24
  • @Servy Yes it did. Here is the screenshot http://jmp.sh/b/VQQmAsiUIoEy9jByahPY – mssrivatsa Sep 05 '13 at 15:26
  • @mssrivatsa Well, perhaps Eric can clarify the versioning system further. You're compiling against the C# 5.0 language specs. Apparently the version of the C# compiler that you are using is capable of compiling against the C# 5.0 language. This is a feature that has always had a lot of attention here, because people post this exacted problem several times a day back when most new C# programmers were using C# 3-4. – Servy Sep 05 '13 at 15:31
  • @Servy I agree with you but i was talking in terms of compiler and you were in terms of language versions – mssrivatsa Sep 05 '13 at 15:36

2 Answers2

7

Since c# 5, the loop variable in foreach is compiled such that it exists within the loop scope rather than outside it.

This means that when you close over the loop variable, you get different results.

Here's what Eric Lippert had to say about the original problem.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
spender
  • 117,338
  • 33
  • 229
  • 351
6

You have accessed a variable inside a closure, therefore the results will differ on different versions of the compiler.

In C# 5.0, the variable is redefined in each iteration of the loop, whereas in previous C# versions it was only defined once.

For more info, see Eric Lippert's great blog post

More notably, the opening paragraph:

UPDATE: We are taking the breaking change. In C# 5, the loop variable of a foreach will be logically inside the loop, and therefore closures will close over a fresh copy of the variable each time. The "for" loop will not be changed. We return you now to our original article.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
Mathew Thompson
  • 55,877
  • 15
  • 127
  • 148