从集合中移除项目的最佳方法
一旦知道了项目,而不是它的索引,什么是最好的方式来处理从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);
我真正想要做的是find要删除的属性(在这种情况下,名称)没有循环通过整个集合,并使用2个额外的variables。
如果要通过某个属性访问集合的成员,则可以考虑使用Dictionary<T>
或KeyedCollection<T>
。 这样你就不必search你正在寻找的物品。
否则,你至less可以这样做:
foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments) { if (spAssignment.Member.Name == shortName) { workspace.RoleAssignments.Remove(spAssignment); break; } }
如果RoleAssignments是一个List<T>
,则可以使用下面的代码。
workSpace.RoleAssignments.RemoveAll(x =>x.Member.Name == shortName);
@smaclell问为什么反向迭代在@ sambo99的评论中更有效率。
有时候更有效率 考虑你有一个人的名单,你想删除或过滤信用评级<1000;
我们有以下数据
"Bob" 999 "Mary" 999 "Ted" 1000
如果我们要迭代,我们很快就会陷入困境
for( int idx = 0; idx < list.Count ; idx++ ) { if( list[idx].Rating < 1000 ) { list.RemoveAt(idx); // whoops! } }
在idx = 0时,我们移除Bob
,然后移动剩下的所有元素。 下一次通过循环idx = 1,但列表[1]现在是Ted
而不是Mary
。 我们最终错误地跳过Mary
。 我们可以使用while循环,并且可以引入更多的variables。
或者,我们只是反向迭代:
for (int idx = list.Count-1; idx >= 0; idx--) { if (list[idx].Rating < 1000) { list.RemoveAt(idx); } }
删除项目左侧的所有索引都保持不变,因此不会跳过任何项目。
如果给出一个从数组中删除的索引列表,则同样的原则适用。 为了保持直观,您需要对列表进行sorting,然后将最高索引中的项目移除到最低。
现在你可以直接使用Linq,直接声明你正在做什么。
list.RemoveAll(o => o.Rating < 1000);
对于这种删除单个项目的情况,它没有更有效的迭代前进或后退。 你也可以使用Linq来做到这一点。
int removeIndex = list.FindIndex(o => o.Name == "Ted"); if( removeIndex != -1 ) { list.RemoveAt(removeIndex); }
对于一个简单的List结构来说,最有效的方法似乎是使用Predicate RemoveAll实现。
例如。
workSpace.RoleAssignments.RemoveAll(x =>x.Member.Name == shortName);
原因是:
- Predicate / Linq RemoveAll方法在List中实现,可以访问存储实际数据的内部数组。 它将移动数据并调整内部数组的大小。
- RemoveAt方法的实现是非常慢的,并将复制整个底层的数据数组到一个新的数组。 这意味着反向迭代对List没有用处
如果你坚持在c#3.0时代实现这一点。 你有2个选项。
- 易于维护的选项。 将所有匹配项目复制到一个新列表中,并交换基础列表。
例如。
List<int> list2 = new List<int>() ; foreach (int i in GetList()) { if (!(i % 2 == 0)) { list2.Add(i); } } list2 = list2;
要么
- 棘手的稍微快一点的选项,包括在不匹配时将列表中的所有数据向下移动,然后调整数组的大小。
如果你真的很频繁地从列表中删除东西,也许像HashTable (.net 1.1)或者Dictionary (.net 2.0)或者HashSet (.net 3.5)这样的其他结构更适合于这个目的。
什么types的收集? 如果是List,则可以使用有用的“RemoveAll”:
int cnt = workspace.RoleAssignments .RemoveAll(spa => spa.Member.Name == shortName)
(当然,如果你没有更新的编译器,你必须使用“委托(SPRoleAssignment水疗){返回spa.Member.Name == shortName;}”而不是好lambda语法。)
如果它不是一个List,但仍然是一个ICollection:
var toRemove = workspace.RoleAssignments .FirstOrDefault(spa => spa.Member.Name == shortName) if (toRemove != null) workspace.RoleAssignments.Remove(toRemove);
这需要Enumerable扩展方法。 (如果你被困在.NET 2.0中,你可以拷贝单声道的)。 如果某个自定义集合不能接受某个项目,但必须带有一个索引,则其他一些Enumerable方法(如Select)将为您传入整数索引。
如果它是一个ICollection
那么你将不会有一个RemoveAll
方法。 这是一个扩展方法,它将做到这一点:
public static void RemoveAll<T>(this ICollection<T> source, Func<T, bool> predicate) { if (source == null) throw new ArgumentNullException("source", "source is null."); if (predicate == null) throw new ArgumentNullException("predicate", "predicate is null."); source.Where(predicate).ToList().ForEach(e => source.Remove(e)); }
根据: http : //phejndorf.wordpress.com/2011/03/09/a-removeall-extension-for-the-collection-class/
这是我的通用解决scheme
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(); }
如果扩展方法支持通过引用传递,它将看起来更简单! 用法:
var result = string[]{"mike", "john", "ali"} result = result.Remove(x => x.Username == "mike").ToArray(); Assert.IsTrue(result.Length == 2);
编辑:确保列表循环保持有效,即使通过递减索引(idx)删除项目。
这是一个很好的方法来做到这一点
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();
还有另一种方法可以根据你如何使用你的collections。 如果您一次下载任务(例如,应用程序运行时),则可以将该集合即时转换为散列表,其中:
短名称=> SPRoleAssignment
如果你这样做了,那么当你想通过短名称来删除一个项目时,你所要做的就是通过键从哈希表中删除项目。
不幸的是,如果你正在加载这些SPRoleAssignments很多,这显然是不会在时间上更具成本效益。 如果您使用的是.NET Framework的新版本,其他人对于使用Linq的build议将会很好,但除此之外,您必须坚持使用您所使用的方法。
这里有很多好的回应。 我特别喜欢lambdaexpression式…非常干净。 然而,我没有指定收集的types。 这是一个SPRoleAssignmentCollection(从MOSS),只有Remove(int)和Remove(SPPrincipal),而不是方便的RemoveAll()。 所以,我已经解决了这个问题,除非有更好的build议。
foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments) { if (spAssignment.Member.Name != shortName) continue; workspace.RoleAssignments.Remove((SPPrincipal)spAssignment.Member); break; }
为了在循环集合时做到这一点,而不是修改一个集合exception,这是我以前采取的方法(注意原始集合末尾的.ToList(),这会在内存中创build另一个集合,那么你可以修改已有的集合)
foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments.ToList()) { if (spAssignment.Member.Name == shortName) { workspace.RoleAssignments.Remove(spAssignment); } }
类似Dictionary Collection的观点,我已经做了这个。
Dictionary<string, bool> sourceDict = new Dictionary<string, bool>(); sourceDict.Add("Sai", true); sourceDict.Add("Sri", false); sourceDict.Add("SaiSri", true); sourceDict.Add("SaiSriMahi", true); var itemsToDelete = sourceDict.Where(DictItem => DictItem.Value == false); foreach (var item in itemsToDelete) { sourceDict.Remove(item.Key); }
注意:上面的代码在.Net客户端configuration文件(3.5和4.5)中也会失败,也有观众提到它在.Net4.0中失败,也不知道哪些设置导致了问题。
所以replace下面的代码(.ToList())为Where语句,以避免该错误。 “collections被修改; 枚举操作可能不会执行“。
var itemsToDelete = sourceDict.Where(DictItem => DictItem.Value == false).ToList();
每MSDN从.Net4.5起,客户端configuration文件将停止运行。 http://msdn.microsoft.com/en-us/library/cc656912(v=vs.110).aspx
首先保存你的项目,而不是删除它们。
var itemsToDelete = Items.Where(x => !!!your condition!!!).ToArray(); for (int i = 0; i < itemsToDelete.Length; ++i) Items.Remove(itemsToDelete[i]);
你需要在你的Item类中覆盖GetHashCode()
。