我正在寻找更好的模式来处理一组元素,每个元素都需要处理,然后根据结果从列表中移除。在foreach(var element in X)循环中不能使用.Remove(element),因为它会导致“集合已修改;可能不执行枚举操作。”异常......您也不能使用for(int i=0;i有没有一种优雅的方式来实现这个?
你不能使用foreach,但你可以向前迭代并在删除项目时管理循环索引变量,像这样:
for (int i = 0; i < elements.Count; i++)
{
if (<condition>)
{
// Decrement the loop counter to iterate this index again, since later elements will get moved down during the remove operation.
elements.RemoveAt(i--);
}
}
对于这种情况,使用for循环是不好的结构。
while
var numbers = new List<int>(Enumerable.Range(1, 3));
while (numbers.Count > 0)
{
numbers.RemoveAt(0);
}
但是,如果你一定要使用 for
var numbers = new List<int>(Enumerable.Range(1, 3));
for (; numbers.Count > 0;)
{
numbers.RemoveAt(0);
}
public static class Extensions
{
public static IList<T> Remove<T>(
this IList<T> numbers,
Func<T, bool> predicate)
{
numbers.ForEachBackwards(predicate, (n, index) => numbers.RemoveAt(index));
return numbers;
}
public static void ForEachBackwards<T>(
this IList<T> numbers,
Func<T, bool> predicate,
Action<T, int> action)
{
for (var i = numbers.Count - 1; i >= 0; i--)
{
if (predicate(numbers[i]))
{
action(numbers[i], i);
}
}
}
}
使用方法:
var numbers = new List<int>(Enumerable.Range(1, 10)).Remove((n) => n > 5);
然而,LINQ已经有RemoveAll()
方法来完成这个任务。
var numbers = new List<int>(Enumerable.Range(1, 10));
numbers.RemoveAll((n) => n > 5);
Where()
方法来过滤和创建新的列表,而不是改变现有的列表,这样更好。不可变性通常是有益的。var numbers = new List<int>(Enumerable.Range(1, 10))
.Where((n) => n <= 5)
.ToList();
在遍历列表并删除其中的项目时,最好使用RemoveAll()
。但人们关注的主要问题是他们必须在循环内部进行一些复杂的操作和/或具有复杂的比较情况。
解决方案仍然是使用RemoveAll()
,但使用以下符号:
var list = new List<int>(Enumerable.Range(1, 10));
list.RemoveAll(item =>
{
// Do some complex operations here
// Or even some operations on the items
SomeFunction(item);
// In the end return true if the item is to be removed. False otherwise
return item > 5;
});
Remove
或者 RemoveAt
操作是故意被设计为困难的,因为这几乎总是错误的做法。你可能能够通过一些巧妙的技巧让它工作,但这会极其缓慢。每次调用 Remove
都需要扫描整个列表以找到要删除的元素。每次调用 RemoveAt
都需要将后续元素向左移动一个位置。因此,任何使用 Remove
或者 RemoveAt
的解决方案都需要二次时间,即 O(n²)。RemoveAll
。否则,以下模式将在线性时间内就地过滤列表,即 O(n)。// Create a list to be filtered
IList<int> elements = new List<int>(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
// Filter the list
int kept = 0;
for (int i = 0; i < elements.Count; i++) {
// Test whether this is an element that we want to keep.
if (elements[i] % 3 > 0) {
// Add it to the list of kept elements.
elements[kept] = elements[i];
kept++;
}
}
// Unfortunately IList has no Resize method. So instead we
// remove the last element of the list until: elements.Count == kept.
while (kept < elements.Count) elements.RemoveAt(elements.Count-1);
list = list.Where(item => ...).ToList();
除非列表非常大,否则在执行此操作时不应出现任何重大性能问题。
假设谓词是元素的布尔属性,如果该属性为真,则应将元素删除:
int i = 0;
while (i < list.Count())
{
if (list[i].predicate == true)
{
list.RemoveAt(i);
continue;
}
i++;
}
foreach(var item in list.ToList()){if(item.Delete) list.Remove(item);}
list.RemoveAll(p=>p.Delete);
但是需要考虑的是,如果在您繁忙地进行删除操作时,其他任务或线程将同时访问同一列表,则可以考虑使用ConcurrentList。
这里有一个选项没有被提到。
如果您不介意在项目中添加一些代码,您可以添加一个扩展到List中,返回一个遍历反向列表的类的实例。
您可以像这样使用它:
foreach (var elem in list.AsReverse())
{
//Do stuff with elem
//list.Remove(elem); //Delete it if you want
}
这是扩展的外观:
public static class ReverseListExtension
{
public static ReverseList<T> AsReverse<T>(this List<T> list) => new ReverseList<T>(list);
public class ReverseList<T> : IEnumerable
{
List<T> list;
public ReverseList(List<T> list){ this.list = list; }
public IEnumerator GetEnumerator()
{
for (int i = list.Count - 1; i >= 0; i--)
yield return list[i];
yield break;
}
}
}
这基本上是没有分配内存的list.Reverse()。
正如一些人所提到的,你仍然会遇到一个一个删除元素的缺点,如果你的列表非常长,这里的某些选项可能更好。但我认为有些人希望拥有list.Reverse()的简单性,而没有内存开销。
foreach(var item in list.ToList())
{
if(item.Delete) list.Remove(item);
}
从第一个列表中创建一个全新的列表。我说“简单”,而不是“正确”,因为创建一个全新的列表可能会对性能产生一定的影响(我没有进行任何基准测试)。我通常更喜欢这种方式,它还可以在克服Linq-To-Entities的限制方面非常有用。
for(i = list.Count()-1;i>=0;i--)
{
item=list[i];
if (item.Delete) list.Remove(item);
}
使用普通的 For 循环方式可以倒序遍历列表。如果正向遍历,当集合大小发生变化时可能会出现问题,但倒序遍历应该总是安全的。
我希望“模式”是这样的:
foreach( thing in thingpile )
{
if( /* condition#1 */ )
{
foreach.markfordeleting( thing );
}
elseif( /* condition#2 */ )
{
foreach.markforkeeping( thing );
}
}
foreachcompleted
{
// then the programmer's choices would be:
// delete everything that was marked for deleting
foreach.deletenow(thingpile);
// ...or... keep only things that were marked for keeping
foreach.keepnow(thingpile);
// ...or even... make a new list of the unmarked items
others = foreach.unmarked(thingpile);
}
Nullable<bool>
),然后在foreach之后使用它来删除/保留项目。 - Dan Bechard