Лучше всего избегать InvalidOperationException: коллекция была изменена?
Очень часто мне нужно что-то вроде этого:
foreach (Line line in lines)
{
if (line.FullfilsCertainConditions())
{
lines.Remove(line)
}
}
Это не работает, потому что я всегда получаю InvalidOperationException
, потому что в течение цикла был изменен Enumerator.
Итак, я изменил все мои петли такого типа на следующее:
List<Line> remove = new List<Line>();
foreach (Line line in lines)
{
if (line.FullfilsCertainConditions())
{
remove.Add(line)
}
}
foreach (Line line in remove) {
{
lines.Remove(line);
}
Я не уверен, что это действительно лучший способ, так как в худшем случае я должен повторять 2 раза по сравнению с исходным списком, поэтому ему нужно время 2n вместо n.
Есть ли лучший способ сделать это?
EDIT:
Я смог сделать это, используя ответ Mark! Но что, если моя коллекция не реализует RemoveAll()?
Например, <
System.Windows.Controls.UIElementCollection
ИЗМЕНИТЬ 2:
Снова с помощью Mark я теперь могу сделать следующий вызов, чтобы удалить все ScatterViewItems:
CollectionUtils.RemoveAll(manager.getWindow().IconDisplay.Items, elem => elem.GetType() == typeof(ScatterViewItem));
Ответы
Ответ 1
Это выпекается непосредственно в List<T>
:
lines.RemoveAll(line => line.FullfilsCertainConditions());
или в С# 2.0:
lines.RemoveAll(delegate(Line line) {
return line.FullfilsCertainConditions();
});
В случае не List<T>
(ваше редактирование вопроса) вы можете обернуть это что-то вроде ниже (untested):
static class CollectionUtils
{
public static void RemoveAll<T>(IList<T> list, Predicate<T> predicate)
{
int count = list.Count;
while (count-- > 0)
{
if (predicate(list[count])) list.RemoveAt(count);
}
}
public static void RemoveAll(IList list, Predicate<object> predicate)
{
int count = list.Count;
while (count-- > 0)
{
if (predicate(list[count])) list.RemoveAt(count);
}
}
}
Так как UIElementCollection
реализует (не общий) IList
, это должно работать. И довольно удобно, с С# 3.0 вы можете добавить this
до IList
/IList<T>
и использовать его как метод расширения. Единственная тонкость заключается в том, что параметр anon-method будет object
, поэтому вам нужно отбросить его.
Ответ 2
Вы можете просто заменить исходный список на отфильтрованный:
lines = lines.Where(line => line.FullfilsCertainConditions()).ToList();
Ответ 3
Создайте новый список instaed:
public IList<Line> GetListWithoutFullfilsCertainConditions(IList<Line> fullList)
{
IList<Line> resultList = new List<Line>(fullList.Count);
foreach (Line line in fullList)
{
if (!line.FullfilsCertainConditions())
{
resultList.Add(line)
}
}
return resultList;
}
Ответ 4
Также вы можете просто использовать цикл while.
int i = 0;
while(i < lines.Count)
{
if (lines[i].FullfilsCertainConditions())
{
lines.RemoveAt(i);
}
else {i++;}
}