我正在寻找一种更好的模式,用于处理每个元素的列表,然后根据结果从列表中删除。

你不能在foreach (var element in X)中使用.Remove(element)(因为它会导致Collection被修改;枚举操作可能无法执行。例外)……你也不能使用for (int I = 0;i < elements.Count();i++)和. removeat (i),因为它破坏了你在集合中相对于i的当前位置。

有没有一种优雅的方式来做到这一点?


当前回答

我会这样做

using System.IO;
using System;
using System.Collections.Generic;

class Author
    {
        public string Firstname;
        public string Lastname;
        public int no;
    }

class Program
{
    private static bool isEven(int i) 
    { 
        return ((i % 2) == 0); 
    } 

    static void Main()
    {    
        var authorsList = new List<Author>()
        {
            new Author{ Firstname = "Bob", Lastname = "Smith", no = 2 },
            new Author{ Firstname = "Fred", Lastname = "Jones", no = 3 },
            new Author{ Firstname = "Brian", Lastname = "Brains", no = 4 },
            new Author{ Firstname = "Billy", Lastname = "TheKid", no = 1 }
        };

        authorsList.RemoveAll(item => isEven(item.no));

        foreach(var auth in authorsList)
        {
            Console.WriteLine(auth.Firstname + " " + auth.Lastname);
        }
    }
}

输出

Fred Jones
Billy TheKid

其他回答

如果决定删除哪些项的函数没有副作用,也没有改变项(这是一个纯函数),一个简单有效的(线性时间)解决方案是:

list.RemoveAll(condition);

如果有副作用,我会使用如下方法:

var toRemove = new HashSet<T>();
foreach(var item in items)
{
     ...
     if(condition)
          toRemove.Add(item);
}
items.RemoveAll(toRemove.Contains);

这仍然是线性时间,假设哈希是好的。但是由于hashset,它增加了内存使用。

最后,如果你的列表只是一个IList<T>,而不是一个list <T>,我建议我的答案,我怎么能做这个特殊的foreach迭代器?与许多其他答案的二次运行时相比,给出IList<T>的典型实现,它将具有线性运行时。

我将从一个过滤掉您不想保留的元素的LINQ查询中重新分配列表。

list = list.Where(item => ...).ToList();

除非列表非常大,否则这样做应该不会有明显的性能问题。

Using Remove or RemoveAt on a list while iterating over that list has intentionally been made difficult, because it is almost always the wrong thing to do. You might be able to get it working with some clever trick, but it would be extremely slow. Every time you call Remove it has to scan through the entire list to find the element you want to remove. Every time you call RemoveAt it has to move subsequent elements 1 position to the left. As such, any solution using Remove or RemoveAt, would require quadratic time, 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);
 foreach (var item in list.ToList()) {
     list.Remove(item);
 }

如果你将".ToList()"添加到列表(或LINQ查询的结果)中,你可以直接从"list"中删除"item",而不用担心"Collection was modified;枚举操作不能执行"错误。编译器会复制“list”,这样你就可以安全地删除数组了。

虽然这种模式不是超级高效,但它有一种自然的感觉,并且对于几乎任何情况都足够灵活。例如,当您想要将每个“项”保存到一个DB,并仅当DB保存成功时才将其从列表中删除。

复制您正在迭代的列表。然后从副本中删除并与原件相互作用。倒退是令人困惑的,并且在并行循环时不能很好地工作。

var ids = new List<int> { 1, 2, 3, 4 };
var iterableIds = ids.ToList();

Parallel.ForEach(iterableIds, id =>
{
    ids.Remove(id);
});