Explain the "unpredictable behavior" of an enumerated property change during foreach

Refer to the LinqPad script below.

When performing the workflow, I take the following set of tasks HasRunfrom the collection (IEnumerable). Iterating over the result set of the Linq query, I change the task to HasRun = true. Debugging shows that initially I received the expected subset of four objects, however, after the subset has been checked, the enumeration suddenly proceeds to the next subset, and the foreach loop continues through this set, and then to the next, etc.

Therefore, when I expect to repeat the iteration four times, it continues until all three subsets (9 elements) are repeated. This is easy to solve with the .ToList()enumerated one, but I wonder if this is intentional behavior.

In Googling, I found references to the “unpredictable behavior” of iteration variables, this old post is one example on which @jon skeet commented , but the latest C # specification (section 8.8.4) does not mention unpredictable behavior, it just mentions problems with assignment, increment and decrease:

A compilation error if the built-in operator tries to change the iteration variable (through the assignment of either ++ and the operators) or pass the iteration variable as the parameter ref or out.

Is this design behavior?

void Main()
{
    List<Foo> foos = new List<UserQuery.Foo>
    {
        new Foo{ SetNbr = 1, HasRun = false },
        new Foo{ SetNbr = 1, HasRun = false },
        new Foo{ SetNbr = 1, HasRun = false },
        new Foo{ SetNbr = 1, HasRun = false },
        new Foo{ SetNbr = 2, HasRun = false },
        new Foo{ SetNbr = 2, HasRun = false },
        new Foo{ SetNbr = 3, HasRun = false },
        new Foo{ SetNbr = 3, HasRun = false },
        new Foo{ SetNbr = 3, HasRun = false }
    };

    //Grab the first subset of Foos where HasRun is false, in order of SetNbr
    var firstNotRunGroup = foos.Where(a => a.SetNbr == (foos.Where(f => f.HasRun == false).Min(f => f.SetNbr)));

    foreach (Foo foo in firstNotRunGroup)
    {
        //foo = new Foo(); < Fails, as expected
        foo.HasRun = true;
        Console.WriteLine(foo.SetNbr);
    }
}

class Foo
{
    public int SetNbr { get; set; }
    public bool HasRun { get; set; }
}

Conclusion:

1 1 1 1 2 2 3 3 3

+4
1

, LINQ , . , LINQ, . , ( ), , , .

, LINQ, , ; , , . , , .

, . , firstNotRunGroup, .

firstNotRunGroup foreach. a, . a.SetNbr - 1. foos , . 1, , . HasRun true .

foreach . foos , - 1, , . .

, foreach , . SetNbr - 2, foo, , , , , 2 . 2 , , .

, . , ; , (-, , ), , , ( ) , , , , .

+8

Source: https://habr.com/ru/post/1660794/


All Articles