Has foreach's use of variables been changed in C# 5?
Asked Answered
S

1

40

In this answer https://mcmap.net/q/35777/-is-quot-access-to-modified-closure-quot-resolved-by-comprehension-syntax Eric Lippert says that "FYI we are highly likely to fix this in the next version of C#; this is a major pain point for developers" with regards to how the foreach loops uses the variable.

In the next version each time you run through the "foreach" loop we will generate a new loop variable rather than closing over the same variable every time. This is a "breaking" change but in the vast majority of cases the "break" will be fixing rather than causing bugs.

I have not been able to find anything indicating that this change has been made yet. Is there any indication that this is how the foreach loop will work in C# 5?

Sanferd answered 24/8, 2012 at 16:3 Comment(7)
The foreach that is being referred to is a C# language feature, not a .NET Framework feature.Nordin
Thanks @BoltClock. I've updated the question to target C# 5. For clarification, though, if I am building a C# project in VS 2012 targeting .Net 4.5, am I likely using C# 5?Sanferd
@a_hardin: You will be using C#5.0 for any project in VS2012 regardless of target framework unless you ask it to use a different compiler. To change the compiler to specific version, go to Project properties, Build tab and click Advanced... at the bottom-right. Then change "Language Version" dropdown.Avoidance
@JeffYates I tried changing the language version in VS2012, but it keeps following the "new" convention (i.e. the foreach variable is an "inside" variable, and a different "copy" is captured in each iteration). Have you actually verified that changing the language version works, for you? (This is mostly of theoretical interest; I don't think anyone would want the C# 4 behavior in practice).Tarrsus
@JeppeStigNielsen: Interesting. I hadn't tried it. Perhaps it will only honour syntax differences and not spec changes. That's surprising. I'll have to take a look when I get chance. HOw did you verify? IL or app behavior?Avoidance
@JeffYates Behavior. I had a small console application for the purpose (not much different from the one in the first comment to your answer). Maybe I did something wrong with the recompilation, so it would be nice if someone tried to reproduce/verify.Tarrsus
Changing the langversion to older than C# 5 will still compile using the new C# 5 convention. The langversion checks only the syntax is compatible with the C# version, it does not change the behaviour of the compiled code. See github.com/dotnet/roslyn/issues/8034Fulk
A
59

This is a change to the C# language, not the .NET framework. Therefore, it only affects code compiled under C# 5.0, regardless of the .NET framework version on which that code will execute.

C# 5.0

Section 8.8.4 of the specification makes it clear that this change has been made. Specifically, page 249 of the C# 5.0 specification states:

foreach (V v in x) embedded-statement

is then expanded to:

{
    E e = ((C)(x)).GetEnumerator();
    try {
        while (e.MoveNext()) {
            V v = (V)(T)e.Current;
            embedded-statement
        }
    }
    finally {
        … // Dispose e
    }
}

And later:

The placement of v inside the while loop is important for how it is captured by any anonymous function occurring in the embedded-statement.

C# 4.0

This change to the specification is clear when comparing with the C# 4.0 specification which states (again, in section 8.8.4, but this time, page 247):

foreach (V v in x) embedded-statement

is then expanded to:

{
    E e = ((C)(x)).GetEnumerator();
    try {
        V v;
        while (e.MoveNext()) {
            v = (V)(T)e.Current;
            embedded-statement
        }
    }
    finally {
        … // Dispose e
    }
}

Note that the variable v is declared outside the loop instead of inside, as it is with C# 5.0.

Note

You can find the C# specification in the installation folder of Visual Studio under VC#\Specifications\1033. This is the case for VS2005, VS2008, VS2010 and VS2012, giving you access to specifications for C# 1.2, 2.0, 3.0, 4.0 and 5.0. You can also find the specifications on MSDN by searching for C# Specification.

Avoidance answered 24/8, 2012 at 16:7 Comment(9)
An example would be good: List<Action> list = new List<Action>(); foreach (var i in new int[] { 1, 2, 3, 4, 5 }) { list.Add(() => Console.WriteLine(i)); } foreach (var f in list) { f(); }Bignoniaceous
I'm not sure an example is necessary here as the OP already understands the issue being discussed and is merely looking for confirmation of the change.Avoidance
That said, thanks for posting one. I can see value in it for future viewers of this question who need context. Perhaps it can be edited in somehow without confusing things. Thanks.Avoidance
Would this affect performance as a new variable has to be created for each iteration?Cisalpine
@ErwinMayer: It might, I don't know without profiling, but I suspect it is negligible and the benefit of not needing to do this when working with closures most likely outweighs any slight performance detriment.Avoidance
why is this earlier c# spec says similar to C# 5.0 foreach msdn.microsoft.com/en-GB/library/aa664754.aspxAmidase
@colinfang: I do not know. I checked the C#1.2 spec (csharpindepth.com/articles/chapter1/Specifications.aspx) and you are correct. It used to be as it is now. However, this was pre-generics and I'm guessing a change was made for C#2.0.Avoidance
@colinfang: It seems it changed for C#3.0. Interesting that this change was made at that time. I do not know the reason why.Avoidance
@colinfang: Based on this comment from Eric Lippert, the spec was wrong prior to C#4 (#8649837)Avoidance

© 2022 - 2024 — McMap. All rights reserved.