如何在同时删除项目时迭代列表?

时间:2014-07-17 15:32:30

标签: c# list iteration

我试图找到一种优雅的方式来迭代列表,同时删除项目。
I know this solution。但我的条件更难:

  • 所有单线程此处
  • 迭代必须前进
  • 每件商品必须完成一次
  • 在处理1件商品时,可以删除多个和随机商品。
  • 项目是复杂而聪明的对象。它们执行自定义方法,并且可以决定删除某些项目(0到all)。
  • (添加和插入也可能发生,但是现在这并不重要,如果有办法同时处理这个问题,那就太棒了)

问题:这可能吗?如果是,怎么样?


我认为标记对象已移除 / 无效。当我稍后再次迭代时,我将删除它们而不要求它们做事情。迭代将经常重复,这就是为什么每个对象在每次迭代时必须正好转1圈的原因。那会有用吗?

<小时/> 这就是我现在处理事情的方式。它并不完美,但会给你提示我希望得到的东西。

的伪代码:

class Foo
{
    public void DoStuff()
    {
        // do other stuff

        if (condition)
            Kill(x); // should result in list.RemoveAt(x) somehow
    }
}

class Program
{
    [STAThread]
    static void Main(string[] args)
    {
        List<Foo> list = new List<Foo>();
        for (int i = 0; i < 15; i++)
            list.Add(new Foo());

        for (int i = 0; i < list.Count; i++)
            list[i].DoStuff();

        Console.ReadKey();
    }
}

<小时/> (这不是一个XY问题。我确定。我已经将这个问题放在我的脑海里多年了,我决定最终找到一个可靠的解决方案。我为此工作在C#中。这不是恶作剧。如果它像这样接缝,我很抱歉。)

感谢您的帮助!

2 个答案:

答案 0 :(得分:6)

你可以做的是在这里使用ObservableCollection,这样迭代集合的代码就可以检测集合在迭代时何时以及如何变异。通过使用ObservableCollection,迭代代码可以在当前索引之前添加项目时递增索引,或者在从当前索引之前删除项目时对其进行描述。

public static IEnumerable<T> IterateWhileMutating<T>(
    this ObservableCollection<T> list)
{
    int i = 0;
    NotifyCollectionChangedEventHandler handler = (_, args) =>
    {
        switch (args.Action)
        {
            case NotifyCollectionChangedAction.Add:
                if (args.NewStartingIndex <= i)
                    i++;
                break;
            case NotifyCollectionChangedAction.Move:
                if (args.NewStartingIndex <= i)
                    i++;
                if (args.OldStartingIndex <= i) //note *not* else if
                    i--;
                break;
            case NotifyCollectionChangedAction.Remove:
                if (args.OldStartingIndex <= i)
                    i--;
                break;
            case NotifyCollectionChangedAction.Reset:
                i = int.MaxValue;//end the sequence
                break;
            default:
                //do nothing
                break;
        }
    };
    try
    {
        list.CollectionChanged += handler;
        for (i = 0; i < list.Count; i++)
        {
            yield return list[i];
        }
    }
    finally
    {
        list.CollectionChanged -= handler;
    }
}

代码取自this other answer of mine。它包含有关在变异时迭代序列的后果的其他切向信息,以及关于此代码及其设计决策含义的一些其他解释。

答案 1 :(得分:3)

  

我想把对象标记为已删除/不活动。

是的,我觉得这样的事情是合理的。我要做的是先收集要删除的所有项目,然后立即将它们全部删除。在代码中,它看起来像:

var toRemove = new HashSet<Item>();

foreach (var item in list)
{
    toRemove.UnionWith(item.GetItemsToRemove());
}

list.RemoveAll(item => toRemove.Contains(item));

这种方法的好处在于它应该是快速的(O(n)),因为从List<T>中删除单个项目是O(n),同时从中删除多个项目也是 O(n)。