Изменение свойств IEnumerator <T>.Current

С большим удивлением я наблюдал следующее поведение сегодня: учитывая класс

class Foo
{
    prop int FooNumber { get; set; }
}

и этот код

IEnumerable<Foo> foos = Enumerable.Range(0,3).Select(new Foo());

foreach (var foo in foos)
    foo.Bar = 5;

foreach (var foo in foos)
   Console.Write(foo.Bar);  // Writes 000

в то время как инициализация foos до new List<Foo>{ new Foo(), new Foo(), new Foo() } заставляет цикл записывать "555".

Мой вопрос: почему это происходит и есть ли способ обойти это без использования .ToList() (которому нужен комментарий, поскольку он здесь не нужен).

Ответ 1

Это происходит потому, что foos динамически создается каждый раз, когда вы перечисляете его. Поэтому во время первой итерации вы устанавливаете значения свойств для объектов, которые больше не ссылаются ни на что после окончания итерации. Вторая итерация работает на только что построенных объектах, которые имеют значение свойства по умолчанию.

Инициализация foos в список "постоянных" объектов изменяет вещи, как и при использовании .ToList() по той же причине ( "фиксированный" список строится и повторяется дважды, исходный динамически созданный IEnumerable повторяется один раз).

Установив, что вы должны использовать .ToList() здесь: в общем, я не чувствую, что ему нужен комментарий, потому что он не является обычным для итерации по динамически созданным последовательностям более одного раза (я считаю, что многие инструменты анализа кода предупреждают об этом), но обязательно напишите один.

Ответ 2

Кажется очевидным, что происходит: каждый раз, когда вы перечисляете, вы создаете новые объекты Foo.

Если вы хотите сохранить значения свойств (Foo.Bar), тогда вам нужно будет где-то удерживать Foo, а ToList() - это простой способ сделать это.