C # Модификация IEnumerable при перечислении с ForEach - PullRequest
4 голосов
/ 10 марта 2011

Это то, что я изучал, чтобы посмотреть, смогу ли я взять то, что было

List<MdiChild> openMdiChildren = new List<MdiChild>();
foreach(child in MdiManager.Pages)
{
    openMdiChildren.Add(child);
}

foreach(child in openMdiChild)
{
   child.Close();
}

и сократить его, чтобы не требовалось 2 foreach петель.

Примечание Я изменил то, как называются объекты, чтобы упростить это для этого примера (они получены от сторонних элементов управления). Но для информации и понимания MdiManager.Pages наследует форму CollectionBase, которая в свою очередь наследует IEnumerable

и MdiChild.Close() удаляют открытый дочерний элемент из коллекции MdiManager.Pages, изменяя таким образом коллекцию и вызывая при перечислении исключение, если коллекция была изменена во время перечисления, например.

foreach(child in MdiManage.Pages)
{
   child.Close();
}

Я смог работать с двойным foreach до

((IEnumerable) MdiManager.Pages).Cast<MdiChild>.ToList()
.ForEach(new Action<MdiChild>(c => c.Close());

Почему это не имеет те же проблемы, связанные с изменением коллекции во время перечисления? Мое лучшее предположение заключается в том, что при перечислении по списку, созданному с помощью вызова ToList, он фактически выполняет действия с соответствующим элементом в коллекции MdiManager.Pages, а не сгенерированным списком.

Редактировать

Я хочу пояснить, что мой вопрос заключается в том, как я могу упростить это, я просто хотел понять, почему не было проблем с изменением коллекции, когда я выполнял ее так, как я ее написал в настоящее время.

Ответы [ 5 ]

8 голосов
/ 10 марта 2011

Ваш звонок на ToList() - это то, что спасает вас здесь, поскольку по сути дублирует то, что вы делаете выше.ToList() фактически создает List<T> (в данном случае List<MdiChild>), который содержит все элементы в MdiManager.Pages, тогда ваш последующий вызов ForEach работает с в этом списке , а не сMdiManager.Pages.

В конце концов, это вопрос стиля.Я лично не фанат функции ForEach (я предпочитаю функции составления запросов, такие как Where и ToList(), из-за их простоты и того факта, что они не предназначены для побочных эффектов на исходный источник,тогда как ForEach нет).

Вы также можете сделать:

foreach(child in MdiManager.Pages.Cast<MdiChild>().ToList())
{
    child.Close();
}

По сути, все три подхода делают одно и то же (они кэшируют содержимое MdiManager.Pages в List<MdiChild>, затем выполняют итерацию по кешированномусписок и вызов Close() на каждый элемент.

0 голосов
/ 10 марта 2011

Вы также можете написать это как:

foreach(var page in MdiManager.Pages.Cast<MdiChild>.ToList())
    page.Close();

В любом случае, когда вы вызываете метод расширения ToList () для IEnumerable; Вы создаете новый список. Удаление из исходной коллекции (в данном случае MdiManager.Pages) не повлияет на вывод списка с помощью ToList ().

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

0 голосов
/ 10 марта 2011

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

0 голосов
/ 10 марта 2011

Когда вы вызываете метод ToList(), вы фактически перечисляете MdiManager.Pages и создаете List<MdiChild> прямо здесь (так что это ваш foreach цикл # 1).Затем, когда метод ForEach() выполняется, он перечислит созданный ранее List<MdiChild> и выполнит ваше действие с каждым элементом (так что foreach loop # 2).

Так что, по сути, это еще один способ выполнить то же самоевещь, просто используя LINQ.

0 голосов
/ 10 марта 2011

Ты в основном прав.

ToList() создает копию перечисления, и, следовательно, вы перечисляете копию.

Вы также можете сделать это, что эквивалентно и показывает, что вы делаете:

var copy = new List<MdiChild>(MdiManager.Pages.Cast<MdiChild>());

foreach(var child in copy)
{
    child.Close();
}

Поскольку вы перечисляете элементы перечисления copy, вам не нужно беспокоиться об изменении коллекции Pages, поскольку каждая ссылка на объект, существовавшая в коллекции Pages, теперь также существует в copy и изменения Pages не влияют на него.

Все остальные методы вызова, ForEach() и приведение типов, излишни и могут быть исключены.

...