我试图找到一种优雅的方式来迭代列表,同时删除项目。
I know this solution。但我的条件更难:
问题:这可能吗?如果是,怎么样?
我认为标记对象已移除 / 无效。当我稍后再次迭代时,我将删除它们而不要求它们做事情。迭代将经常重复,这就是为什么每个对象在每次迭代时必须正好转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#中。这不是恶作剧。如果它像这样接缝,我很抱歉。)
感谢您的帮助!
答案 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)。