从集合中删除项目的最佳方法
本文关键字:最佳 方法 删除项目 集合 | 更新日期: 2023-09-27 17:47:47
在 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);
我真正想做的是按属性(在本例中为 name(找到要删除的项目,而无需遍历整个集合并使用 2 个附加变量。
如果角色分配是List<T>
则可以使用以下代码。
workSpace.RoleAssignments.RemoveAll(x =>x.Member.Name == shortName);
通过集合成员的属性之一访问成员,可以考虑改用Dictionary<T>
或KeyedCollection<T>
。这样,您就不必搜索要查找的项目。
否则,您至少可以这样做:
foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments)
{
if (spAssignment.Member.Name == shortName)
{
workspace.RoleAssignments.Remove(spAssignment);
break;
}
}
@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 循环,并且可以引入更多变量。
或者,我们只是反向迭代:
for (int idx = list.Count-1; idx >= 0; idx--)
{
if (list[idx].Rating < 1000)
{
list.RemoveAt(idx);
}
}
已删除项目左侧的所有索引保持不变,因此您不会跳过任何项目。
如果为您提供要从数组中删除的索引列表,则同样的原则适用。为了保持简单,您需要对列表进行排序,然后从最高索引到最低索引删除项目。
现在,您只需使用 Linq 并以直接的方式声明您正在执行的操作。
list.RemoveAll(o => o.Rating < 1000);
对于删除单个项目的情况,向前或向后迭代的效率不高。您也可以为此使用 Linq。
int removeIndex = list.FindIndex(o => o.Name == "Ted");
if( removeIndex != -1 )
{
list.RemoveAt(removeIndex);
}
如果它是一个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/
对于简单的列表结构,最有效的方法似乎是使用谓词RemoveAll实现。
例如。
workSpace.RoleAssignments.RemoveAll(x =>x.Member.Name == shortName);
原因是:
- Predicate/Linq RemoveAll 方法在 List 中实现,可以访问存储实际数据的内部数组。它将移动数据并调整内部数组的大小。
- RemoveAt 方法实现非常慢,并且会将整个基础数据数组复制到新数组中。这意味着反向迭代对列表毫无用 处
如果您坚持在 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(或字典(.net 2.0(或HashSet(.net 3.5(更适合此目的。
集合是什么类型?如果是列表,您可以使用有用的"全部删除":
int cnt = workspace.RoleAssignments
.RemoveAll(spa => spa.Member.Name == shortName)
(这适用于 .NET 2.0。当然,如果你没有更新的编译器,你将不得不使用"delegate (SPRoleAssignment spa( { return spa.Member.Name == shortName; }"而不是漂亮的lambda语法。
如果它不是列表,但仍然是一个 ICollection,则采用另一种方法:
var toRemove = workspace.RoleAssignments
.FirstOrDefault(spa => spa.Member.Name == shortName)
if (toRemove != null) workspace.RoleAssignments.Remove(toRemove);
这需要可枚举扩展方法。(如果您停留在 .NET 2.0 上,则可以复制 Mono 版本(。如果是某个无法获取项但必须获取索引的自定义集合,则其他一些 Enumerable 方法(如 Select(会为你传入整数索引。
的通用解决方案
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();
根据您使用集合的方式,可以采用另一种方法。如果您一次性下载作业(例如,当应用程序运行时(,则可以将集合即时转换为哈希表,其中:
短名称 => SPRoleAssignment
如果这样做,那么当您想按短名称删除项目时,您需要做的就是按键从哈希表中删除该项目。
不幸的是,如果您大量加载这些 SPRoleAssignment,那么就时间而言,这显然不会更具成本效益。如果您使用的是新版本的 .NET Framework,其他人提出的关于使用 Linq 的建议会很好,但除此之外,您必须坚持使用您正在使用的方法。
与字典收藏的观点类似,我已经这样做了。
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客户端配置文件(3.5和4.5(中失败,一些观众也提到它是在 中失败。Net4.0也不确定哪些设置导致了问题。
因此,请替换为以下代码(.ToList((( for Where 语句,以避免该错误。"收藏被修改;枚举操作可能无法执行。
var itemsToDelete = sourceDict.Where(DictItem => DictItem.Value == false).ToList();
根据 MSDN 从 .Net4.5 及以上客户资料已停产。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()
。
最好的方法是使用 linq。
示例类:
public class Product
{
public string Name { get; set; }
public string Price { get; set; }
}
林克查询:
var subCollection = collection1.RemoveAll(w => collection2.Any(q => q.Name == w.Name));
如果Name
与collection2
中的任何元素Name
匹配,则此查询将从collection1
中删除所有元素
记得使用:using System.Linq;
要在循环访问集合时执行此操作,而不是获取修改集合异常,这是我过去采用的方法(请注意 .ToList(( 在原始集合的末尾,这会在内存中创建另一个集合,然后你可以修改现有的集合(
foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments.ToList())
{
if (spAssignment.Member.Name == shortName)
{
workspace.RoleAssignments.Remove(spAssignment);
}
}
如果你有List<T>
,那么List<T>.RemoveAll
是你最好的选择。没有比这更有效率的了。在内部,它使阵列一次性移动,更不用说它是O(N(。
如果你得到的只是一个IList<T>
或一个ICollection<T>
你大致有这三个选择:
public static void RemoveAll<T>(this IList<T> ilist, Predicate<T> predicate) // O(N^2)
{
for (var index = ilist.Count - 1; index >= 0; index--)
{
var item = ilist[index];
if (predicate(item))
{
ilist.RemoveAt(index);
}
}
}
或
public static void RemoveAll<T>(this ICollection<T> icollection, Predicate<T> predicate) // O(N)
{
var nonMatchingItems = new List<T>();
// Move all the items that do not match to another collection.
foreach (var item in icollection)
{
if (!predicate(item))
{
nonMatchingItems.Add(item);
}
}
// Clear the collection and then copy back the non-matched items.
icollection.Clear();
foreach (var item in nonMatchingItems)
{
icollection.Add(item);
}
}
或
public static void RemoveAll<T>(this ICollection<T> icollection, Func<T, bool> predicate) // O(N^2)
{
foreach (var item in icollection.Where(predicate).ToList())
{
icollection.Remove(item);
}
}
选择 1 或 2。
1 的内存更轻,如果要执行的删除更少(即谓词大多数时候都是假的(,则速度更快。
如果要执行更多删除操作,则 2 会更快。
3 是最干净的代码,但 IMO 性能不佳。同样,这一切都取决于输入数据。
有关一些基准测试的详细信息,请参阅 https://github.com/dotnet/BenchmarkDotNet/issues/1505
这里有很多很好的回应;我特别喜欢lambda表达式...很干净。 但是,我没有指定集合的类型。 这是一个 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;
}