Element from IEnumerable changed inside foreach, but change is not saved in collection

I had an interesting problem today, this is a reproduction:

class TestListAndEnumerable
{
    public static void Test()
    {
        bool b1 = GetBool1(); // returns false
        bool b2 = GetBool2(); // returns true
    }

    private static bool GetBool1()
    {
        IEnumerable<BoolContainer> list = new List<bool> { false }.Select(x => new BoolContainer { Value = x });

        foreach (BoolContainer item in list)
        {
            item.Value = true;
        }

        return list.Select(x => x.Value).First();
    }

    private static bool GetBool2()
    {
        List<BoolContainer> list = new List<bool> { false }.Select(x => new BoolContainer { Value = x }).ToList();

        foreach (BoolContainer item in list)
        {
            item.Value = true;
        }

        return list.Select(x => x.Value).First();
    }

    private class BoolContainer
    {
        public bool Value { get; set; }
    }
}

      

Internally, GetBool1()

changing the value item

does not affect the variable list

.
GetBool2()

works as expected.

Does anyone know why this is happening?

[Regarding the duplicate tag]

I think this is fundamentally the same problem as in this question , but the question is more concise here and I decided to leave it.

+3


source to share


1 answer


It's a matter of lazy LINQ evaluation. Each time you iterate list

over GetBool1()

, you create a new sequence of values. Changing a property Value

on one of these objects doesn't change anything.

Compare this to GetBool2()

where you got the call ToList()

, which means you are creating List<T>

with a sequence of objects. Now every time you iterate over this list, I get references to the same objects ... so if you change the value in one of these objects, you will see it next time.

If you change your Select argument

to



x => {Console.WriteLine ("Choice!"); return new BoolContainer {Value = x}};

... then add the appropriate additional calls Console.WriteLine

so you can see when this happens, you will see that the delegate is never called in GetBool2()

after the call ToList()

, but in GetBool1()

it will be called every time you iterate over the sequence.

+12


source







All Articles