C# 从集合中删除项目的最佳方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/207038/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Best way to remove items from a collection
提问by Dan
What is the best way to approach removing items from a collection in C#, once the item is known, but not it's index. This is one way to do it, but it seems inelegant at best.
一旦项目已知,但不是索引,从 C# 中的集合中删除项目的最佳方法是什么?这是一种方法,但充其量似乎不优雅。
//Remove the existing role assignment for the user.
int cnt = 0;
int assToDelete = 0;
foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments)
{
if (spAssignment.Member.Name == shortName)
{
assToDelete = cnt;
}
cnt++;
}
workspace.RoleAssignments.Remove(assToDelete);
What I would really like to do is find the item to remove by property (in this case, name) without looping through the entire collection and using 2 additional variables.
我真正想做的是找到要按属性(在本例中为名称)删除的项目,而不需要遍历整个集合并使用 2 个附加变量。
采纳答案by Jon B
If you want to access members of the collection by one of their properties, you might consider using a Dictionary<T>
or KeyedCollection<T>
instead. This way you don't have to search for the item you're looking for.
如果要通过集合成员的属性之一访问集合成员,则可以考虑使用Dictionary<T>
或KeyedCollection<T>
代替。这样您就不必搜索您要查找的项目。
Otherwise, you could at least do this:
否则,你至少可以这样做:
foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments)
{
if (spAssignment.Member.Name == shortName)
{
workspace.RoleAssignments.Remove(spAssignment);
break;
}
}
回答by JaredPar
If RoleAssignments is a List<T>
you can use the following code.
如果 RoleAssignments 是 aList<T>
您可以使用以下代码。
workSpace.RoleAssignments.RemoveAll(x =>x.Member.Name == shortName);
回答by MichaelGG
What type is the collection? If it's List, you can use the helpful "RemoveAll":
集合是什么类型?如果是列表,您可以使用有用的“RemoveAll”:
int cnt = workspace.RoleAssignments
.RemoveAll(spa => spa.Member.Name == shortName)
(This works in .NET 2.0. Of course, if you don't have the newer compiler, you'll have to use "delegate (SPRoleAssignment spa) { return spa.Member.Name == shortName; }" instead of the nice lambda syntax.)
(这适用于 .NET 2.0。当然,如果您没有较新的编译器,则必须使用“delegate (SPRoleAssignment spa) { return spa.Member.Name == shortName; }” 而不是 nice lambda 语法。)
Another approach if it's not a List, but still an ICollection:
如果它不是列表,但仍然是 ICollection,则另一种方法:
var toRemove = workspace.RoleAssignments
.FirstOrDefault(spa => spa.Member.Name == shortName)
if (toRemove != null) workspace.RoleAssignments.Remove(toRemove);
This requires the Enumerable extension methods. (You can copy the Mono ones in, if you are stuck on .NET 2.0). If it's some custom collection that cannot take an item, but MUST take an index, some of the other Enumerable methods, such as Select, pass in the integer index for you.
这需要 Enumerable 扩展方法。(如果您坚持使用 .NET 2.0,您可以将 Mono 复制进来)。如果是一些不能带项目但必须带索引的自定义集合,则其他一些 Enumerable 方法,例如 Select,会为您传入整数索引。
回答by Sam Saffron
For a simple List structure the most efficient way seems to be using the Predicate RemoveAll implementation.
对于简单的 List 结构,最有效的方法似乎是使用 Predicate RemoveAll 实现。
Eg.
例如。
workSpace.RoleAssignments.RemoveAll(x =>x.Member.Name == shortName);
The reasons are:
原因是:
- The Predicate/Linq RemoveAll method is implemented in List and has access to the internal array storing the actual data. It will shift the data and resize the internal array.
- The RemoveAt method implementation is quite slow, and will copy the entire underlying array of data into a new array. This means reverse iteration is useless for List
- Predicate/Linq RemoveAll 方法在 List 中实现,可以访问存储实际数据的内部数组。它将移动数据并调整内部数组的大小。
- RemoveAt 方法的实现非常慢,并且会将整个底层数据数组复制到一个新数组中。这意味着反向迭代对于 List 是无用的
If you are stuck implementing this in a the pre c# 3.0 era. You have 2 options.
如果您在 c# 3.0 之前的时代坚持实现这一点。您有 2 个选择。
- The easily maintainable option. Copy all the matching items into a new list and and swap the underlying list.
- 易于维护的选项。将所有匹配项复制到新列表中并交换基础列表。
Eg.
例如。
List<int> list2 = new List<int>() ;
foreach (int i in GetList())
{
if (!(i % 2 == 0))
{
list2.Add(i);
}
}
list2 = list2;
Or
或者
- The tricky slightly faster option, which involves shifting all the data in the list down when it does not match and then resizing the array.
- 棘手的稍微快一点的选项,它涉及将列表中的所有数据在不匹配时向下移动,然后调整数组的大小。
If you are removing stuff really frequently from a list, perhaps another structure like a HashTable(.net 1.1) or a Dictionary(.net 2.0) or a HashSet(.net 3.5) are better suited for this purpose.
如果您真的经常从列表中删除内容,也许另一种结构,如HashTable(.net 1.1) 或Dictionary(.net 2.0) 或HashSet(.net 3.5) 更适合此目的。
回答by Robert Paulson
@smaclell asked why reverse iteration was more efficient in in a comment to @sambo99.
@smaclell 在对@sambo99 的评论中询问为什么反向迭代更有效。
Sometimesit's more efficient. Consider you have a list of people, and you want to remove or filter all customers with a credit rating < 1000;
有时效率更高。假设您有一个人员列表,并且您想要删除或过滤信用等级 < 1000 的所有客户;
We have the following data
我们有以下数据
"Bob" 999
"Mary" 999
"Ted" 1000
If we were to iterate forward, we'd soon get into trouble
如果我们向前迭代,我们很快就会陷入困境
for( int idx = 0; idx < list.Count ; idx++ )
{
if( list[idx].Rating < 1000 )
{
list.RemoveAt(idx); // whoops!
}
}
At idx = 0 we remove Bob
, which then shifts all remaining elements left. The next time through the loop idx = 1, but
list[1] is now Ted
instead of Mary
. We end up skipping Mary
by mistake. We could use a while loop, and we could introduce more variables.
在 idx = 0 时,我们删除Bob
,然后将所有剩余元素向左移动。下一次通过循环 idx = 1,但 list[1] 现在Ted
不是Mary
。我们最终Mary
错误地跳过了。我们可以使用一个while循环,我们可以引入更多的变量。
Or, we just reverse iterate:
或者,我们只是反向迭代:
for (int idx = list.Count-1; idx >= 0; idx--)
{
if (list[idx].Rating < 1000)
{
list.RemoveAt(idx);
}
}
All the indexes to the left of the removed item stay the same, so you don't skip any items.
已删除项目左侧的所有索引保持不变,因此您不会跳过任何项目。
The same principle applies if you're given a list of indexes to remove from an array. In order to keep things straight you need to sort the list and then remove the items from highest index to lowest.
如果给定要从数组中删除的索引列表,则同样的原则也适用。为了使事情保持直截了当,您需要对列表进行排序,然后从最高索引到最低索引删除项目。
Now you can just use Linq and declare what you're doing in a straightforward manner.
现在您可以使用 Linq 并以直接的方式声明您正在执行的操作。
list.RemoveAll(o => o.Rating < 1000);
For this case of removing a single item, it's no more efficient iterating forwards or backwards. You could also use Linq for this.
对于删除单个项目的这种情况,向前或向后迭代不再有效。您也可以为此使用 Linq。
int removeIndex = list.FindIndex(o => o.Name == "Ted");
if( removeIndex != -1 )
{
list.RemoveAt(removeIndex);
}
回答by Ed Altorfer
There is another approach you can take depending on how you're using your collection. If you're downloading the assignments one time (e.g., when the app runs), you could translate the collection on the fly into a hashtable where:
根据您使用收藏的方式,您还可以采用另一种方法。如果您一次性下载作业(例如,当应用程序运行时),您可以即时将集合转换为哈希表,其中:
shortname => SPRoleAssignment
简称 => SPRoleAssignment
If you do this, then when you want to remove an item by short name, all you need to do is remove the item from the hashtable by key.
如果你这样做,那么当你想通过短名称删除一个项目时,你需要做的就是通过键从哈希表中删除该项目。
Unfortunately, if you're loading these SPRoleAssignments a lot, that obviously isn't going to be any more cost efficient in terms of time. The suggestions other people made about using Linq would be good if you're using a new version of the .NET Framework, but otherwise, you'll have to stick to the method you're using.
不幸的是,如果您大量加载这些 SPRoleAssignments,那么就时间而言,这显然不会更具成本效益。如果您使用的是 .NET Framework 的新版本,其他人就使用 Linq 提出的建议会很好,但否则,您就必须坚持使用的方法。
回答by Dan
A lot of good responses here; I especially like the lambda expressions...very clean. I was remiss, however, in not specifying the type of Collection. This is a SPRoleAssignmentCollection (from MOSS) that only has Remove(int) and Remove(SPPrincipal), not the handy RemoveAll(). So, I have settled on this, unless there is a better suggestion.
这里有很多很好的回应;我特别喜欢 lambda 表达式……非常干净。然而,我疏忽了没有指定 Collection 的类型。这是一个 SPRoleAssignmentCollection(来自 MOSS),它只有 Remove(int) 和 Remove(SPPrincipal),而不是方便的 RemoveAll()。所以,我已经解决了这个问题,除非有更好的建议。
foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments)
{
if (spAssignment.Member.Name != shortName) continue;
workspace.RoleAssignments.Remove((SPPrincipal)spAssignment.Member);
break;
}
回答by Dan
Here is a pretty good way to do it
这是一个很好的方法来做到这一点
http://support.microsoft.com/kb/555972
http://support.microsoft.com/kb/555972
System.Collections.ArrayList arr = new System.Collections.ArrayList();
arr.Add("1");
arr.Add("2");
arr.Add("3");
/*This throws an exception
foreach (string s in arr)
{
arr.Remove(s);
}
*/
//where as this works correctly
Console.WriteLine(arr.Count);
foreach (string s in new System.Collections.ArrayList(arr))
{
arr.Remove(s);
}
Console.WriteLine(arr.Count);
Console.ReadKey();
回答by Jalal El-Shaer
This is my generic solution
这是我的通用解决方案
public static IEnumerable<T> Remove<T>(this IEnumerable<T> items, Func<T, bool> match)
{
var list = items.ToList();
for (int idx = 0; idx < list.Count(); idx++)
{
if (match(list[idx]))
{
list.RemoveAt(idx);
idx--; // the list is 1 item shorter
}
}
return list.AsEnumerable();
}
It would look much simpler if extension methods support passing by reference ! usage:
如果扩展方法支持通过引用传递,它看起来会简单得多!用法:
var result = string[]{"mike", "john", "ali"}
result = result.Remove(x => x.Username == "mike").ToArray();
Assert.IsTrue(result.Length == 2);
EDIT: ensured that the list looping remains valid even when deleting items by decrementing the index (idx).
编辑:确保即使在通过减少索引 (idx) 删除项目时,列表循环仍然有效。
回答by Anthony Shaw
To do this while looping through the collection and not to get the modifying a collection exception, this is the approach I've taken in the past (note the .ToList() at the end of the original collection, this creates another collection in memory, then you can modify the existing collection)
要在循环遍历集合时执行此操作而不是修改集合异常,这是我过去采用的方法(注意原始集合末尾的 .ToList(),这会在内存中创建另一个集合,然后可以修改现有的集合)
foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments.ToList())
{
if (spAssignment.Member.Name == shortName)
{
workspace.RoleAssignments.Remove(spAssignment);
}
}