6

我正在尝试从 System.Collections.Generic.LinkedList 中删除一个节点,其中 T 是具有多个属性的对象。我想根据匹配的属性之一删除节点,例如T.paint.color = "blue"。起初我试过:

foreach (Car carNode in carList)
{
    if (carNode.paint.color == "blue")
    {
         carList.Remove(carNode);
    }
}

当然,这会因“枚举数被实例化后集合被修改”错误而失败。MSDN 上的示例是一个简单的字符串数组,使用如下:

sentence.Remove("old");

我的问题是如何(或是否)我可以使用(使用伪代码)之类的东西:

carList.Remove(the node where carList.paint.color == "blue");

谢谢。

4

1 回答 1

14

所以这里有两个选择。最容易编码但效率最低的选项是抓住所有要删除的项目,然后在找到它们后将它们全部删除:

var carsToRemove = carList.Where(carNode => carNode.paint.color == "blue")
    .ToList();

foreach(var car in carsToRemove)
    carList.Remove(car);

请注意,ToList这里的调用非常重要;根本Where不允许推迟底层列表的迭代,否则你会得到相同的并发修改错误。

这里有两个问题。首先,您需要将所有要删除的项目保存在内存中。还不错,除非你有很多(我的意思是很多)。更大的问题是你没有节点对象,你有节点的值,所以你需要从头开始遍历整个列表来找到每个对象并删除它们。您已将 O(n) 操作转换为 O(n^2) 操作。即使列表不是巨大的,但只是大小不一,这也是一个问题。

相反,我们只需要在不使用 a 的情况下遍历集合,foreach以便我们拥有对Node对象的引用,并且通过正确管理何时/如何遍历和修改集合,我们不会获得并发修改异常。

var currentNode = list.First;
while (currentNode != null)
{
    if (currentNode.Value.color == "blue")
    {
        var toRemove = currentNode;
        currentNode = currentNode.Next;
        list.Remove(toRemove);
    }
    else
    {
        currentNode = currentNode.Next;
    }
}

它不是那么漂亮,但它会更有效率。

现在,理想情况下LinkedList会有一种RemoveAll方法,这样您就不必一直为此烦恼。可悲的是,它没有。不过,从好的方面来说,您可以添加自己的扩展方法:

public static void RemoveAll<T>(this LinkedList<T> list, Func<T, bool> predicate)
{
    var currentNode = list.First;
    while (currentNode != null)
    {
        if (predicate(currentNode.Value))
        {
            var toRemove = currentNode;
            currentNode = currentNode.Next;
            list.Remove(toRemove);
        }
        else
        {
            currentNode = currentNode.Next;
        }
    }
}

现在我们可以写:

carList.RemoveAll(car => car.paint.color == "blue");
于 2013-10-23T18:33:30.980 回答