从列表中删除项目的智能方法而在c#中枚举
本文关键字:而在 枚举 方法 列表 删除项目 智能 | 更新日期: 2023-09-27 18:06:34
我有一个经典的例子,试图从集合中删除一个项目,同时在循环中枚举它:
List<int> myIntCollection = new List<int>();
myIntCollection.Add(42);
myIntCollection.Add(12);
myIntCollection.Add(96);
myIntCollection.Add(25);
foreach (int i in myIntCollection)
{
if (i == 42)
myIntCollection.Remove(96); // The error is here.
if (i == 25)
myIntCollection.Remove(42); // The error is here.
}
在发生更改后的迭代开始时,抛出InvalidOperationException
,因为枚举数不喜欢底层集合发生更改。
我需要在迭代时对集合进行更改。有许多模式可以用来避免这个,但它们似乎都没有一个好的解决方案:
不要在这个循环中删除,而是保留一个单独的"delete List",在主循环之后处理。
这通常是一个很好的解决方案,但在我的情况下,我需要物品立即消失,直到"等待"真正删除条目的主循环改变了我代码的逻辑流程。
不删除项目,只需在项目上设置一个标志并将其标记为非活动。然后添加模式1的功能来清理列表。
这个将满足我的所有需求,但这意味着必须更改许多代码,以便在每次访问项目时检查非活动标志。这是太多的管理,我不喜欢。
以某种方式将模式2的思想合并到从
List<T>
派生的类中。此Superlist将处理非活动标志,即事后删除对象,并且不会将标记为非活动的项公开给枚举消费者。基本上,它只是封装了模式2(以及随后的模式1)的所有思想。这样的类存在吗?有人知道这个的代码吗?还是有更好的办法?
我被告知访问
myIntCollection.ToArray()
而不是myIntCollection
将解决问题,并允许我在循环内删除。这对我来说似乎是一个糟糕的设计模式,或者它可能是好的?
细节:
列表将包含许多项目,我将只删除其中的一些。
在循环中,我将执行各种进程,添加,删除等,因此解决方案需要相当通用。
我需要删除的项可能不是循环中的当前项。例如,我可能在30项循环中的第10项上,需要删除第6项或第26项。因此,向后遍历数组将不再工作。; o (
最好的解决方案通常是使用RemoveAll()
方法:
myList.RemoveAll(x => x.SomeProp == "SomeValue");
或者,如果您需要某些元素删除:
MyListType[] elems = new[] { elem1, elem2 };
myList.RemoveAll(x => elems.Contains(x));
当然,这假设你的循环仅仅是为了删除目的。如果确实需要对进行额外的处理,那么最好的方法通常是使用for
或while
循环,因为这样就不用使用枚举数了:
for (int i = myList.Count - 1; i >= 0; i--)
{
// Do processing here, then...
if (shouldRemoveCondition)
{
myList.RemoveAt(i);
}
}
返回确保你不会跳过任何元素。
对编辑的响应:
如果要删除任意元素,最简单的方法可能是跟踪要删除的元素,然后立即将它们全部删除。像这样:
List<int> toRemove = new List<int>();
foreach (var elem in myList)
{
// Do some stuff
// Check for removal
if (needToRemoveAnElement)
{
toRemove.Add(elem);
}
}
// Remove everything here
myList.RemoveAll(x => toRemove.Contains(x));
如果您必须枚举List<T>
并从中删除,那么我建议简单地使用while
循环而不是foreach
var index = 0;
while (index < myList.Count) {
if (someCondition(myList[index])) {
myList.RemoveAt(index);
} else {
index++;
}
}
我知道这篇文章很老了,但我想我应该分享一下对我有用的东西。
创建一个列表的副本用于枚举,然后在每个循环中,您可以对复制的值进行处理,并删除/添加/使用源列表。
private void ProcessAndRemove(IList<Item> list)
{
foreach (var item in list.ToList())
{
if (item.DeterminingFactor > 10)
{
list.Remove(item);
}
}
}
当你需要遍历列表并可能在循环过程中修改它时,你最好使用for循环:
for (int i = 0; i < myIntCollection.Count; i++)
{
if (myIntCollection[i] == 42)
{
myIntCollection.Remove(i);
i--;
}
}
当然,您必须小心,例如,每当删除一项时,我就递减i
,否则我们将跳过条目(另一种选择是向后浏览列表)。
如果你有Linq,那么你应该使用RemoveAll
作为dlev的建议。
在枚举列表时,将要保留的列表添加到新列表中。然后,将新列表赋值给myIntCollection
List<int> myIntCollection=new List<int>();
myIntCollection.Add(42);
List<int> newCollection=new List<int>(myIntCollection.Count);
foreach(int i in myIntCollection)
{
if (i want to delete this)
///
else
newCollection.Add(i);
}
myIntCollection = newCollection;
让我们为您添加代码:
List<int> myIntCollection=new List<int>();
myIntCollection.Add(42);
myIntCollection.Add(12);
myIntCollection.Add(96);
myIntCollection.Add(25);
如果您想在foreach中更改列表,您必须键入.ToList()
foreach(int i in myIntCollection.ToList())
{
if (i == 42)
myIntCollection.Remove(96);
if (i == 25)
myIntCollection.Remove(42);
}
对于那些可能有帮助的人,我编写了这个扩展方法来删除与谓词匹配的项目并返回已删除项目的列表。
public static IList<T> RemoveAllKeepRemoved<T>(this IList<T> source, Predicate<T> predicate)
{
IList<T> removed = new List<T>();
for (int i = source.Count - 1; i >= 0; i--)
{
T item = source[i];
if (predicate(item))
{
removed.Add(item);
source.RemoveAt(i);
}
}
return removed;
}
int[] tmp = new int[myIntCollection.Count ()];
myIntCollection.CopyTo(tmp);
foreach(int i in tmp)
{
myIntCollection.Remove(42); //The error is no longer here.
}
如果您对高性能感兴趣,可以使用两个列表。下面的代码最大限度地减少了垃圾收集,最大限度地提高了内存局域性,并且永远不会从列表中实际删除一个项,如果它不是最后一个项,那么这是非常低效的。
private void RemoveItems()
{
_newList.Clear();
foreach (var item in _list)
{
item.Process();
if (!item.NeedsRemoving())
_newList.Add(item);
}
var swap = _list;
_list = _newList;
_newList = swap;
}
我想我将分享我的解决方案,一个类似的问题,我需要从一个列表中删除项目,同时处理它们。
基本上就是"foreach"这将在迭代后从列表中删除项。
我的测试:
var list = new List<TempLoopDto>();
list.Add(new TempLoopDto("Test1"));
list.Add(new TempLoopDto("Test2"));
list.Add(new TempLoopDto("Test3"));
list.Add(new TempLoopDto("Test4"));
list.PopForEach((item) =>
{
Console.WriteLine($"Process {item.Name}");
});
Assert.That(list.Count, Is.EqualTo(0));
我用扩展方法"PopForEach"解决了这个问题。这将执行一个操作,然后从列表中删除项目。
public static class ListExtensions
{
public static void PopForEach<T>(this List<T> list, Action<T> action)
{
var index = 0;
while (index < list.Count) {
action(list[index]);
list.RemoveAt(index);
}
}
}
希望这对任何人都有帮助。
当前您正在使用列表。如果你能用字典来代替,那就容易多了。我做了一些假设,假设你真的在使用一个类,而不仅仅是一个整数列表。如果您有某种形式的唯一密钥,这将是可行的。在字典中,object可以是你拥有的任何类,int可以是任何唯一的键。
Dictionary<int, object> myIntCollection = new Dictionary<int, object>();
myIntCollection.Add(42, "");
myIntCollection.Add(12, "");
myIntCollection.Add(96, "");
myIntCollection.Add(25, "");
foreach (int i in myIntCollection.Keys)
{
//Check to make sure the key wasn't already removed
if (myIntCollection.ContainsKey(i))
{
if (i == 42) //You can test against the key
myIntCollection.Remove(96);
if (myIntCollection[i] == 25) //or you can test against the value
myIntCollection.Remove(42);
}
}
或者你可以用
Dictionary<myUniqueClass, bool> myCollection; //Bool is just an empty place holder
好处是你可以对底层字典做任何你想做的事情,键枚举器不在乎,但它也不会随着添加或删除条目而更新。