74

一旦项目已知,但不是它的索引,从 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);

我真正想做的是找到要按属性(在本例中为名称)删除的项目,而无需遍历整个集合并使用 2 个附加变量。

4

15 回答 15

138

如果 RoleAssignments 是 aList<T>您可以使用以下代码。

workSpace.RoleAssignments.RemoveAll(x =>x.Member.Name == shortName);
于 2008-10-16T01:04:31.347 回答
28

如果您想通过其中一个属性访问集合的成员,您可以考虑使用Dictionary<T>orKeyedCollection<T>代替。这样您就不必搜索您要查找的项目。

否则,您至少可以这样做:

foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments)
{
    if (spAssignment.Member.Name == shortName)
    {
        workspace.RoleAssignments.Remove(spAssignment);
        break;
    }
}
于 2008-10-16T00:50:48.597 回答
23

@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,但 list[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);
}
于 2008-10-16T04:33:47.430 回答
12

如果是,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/

于 2013-04-17T11:16:29.827 回答
10

对于简单的 List 结构,最有效的方法似乎是使用 Predicate RemoveAll 实现。

例如。

 workSpace.RoleAssignments.RemoveAll(x =>x.Member.Name == shortName);

原因是:

  1. Predicate/Linq RemoveAll 方法在 List 中实现,并且可以访问存储实际数据的内部数组。它将移动数据并调整内部数组的大小。
  2. 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) 等其他结构可能更适合此目的。

于 2008-10-16T01:12:02.780 回答
7

集合是什么类型的?如果是列表,您可以使用有用的“RemoveAll”:

int cnt = workspace.RoleAssignments
                      .RemoveAll(spa => spa.Member.Name == shortName)

(这适用于 .NET 2.0。当然,如果您没有较新的编译器,则必须使用“delegate (SPRoleAssignment spa) { return spa.Member.Name == shortName; }” 而不是 nice lambda 语法。)

如果它不是列表,但仍然是 ICollection,则另一种方法:

   var toRemove = workspace.RoleAssignments
                              .FirstOrDefault(spa => spa.Member.Name == shortName)
   if (toRemove != null) workspace.RoleAssignments.Remove(toRemove);

这需要 Enumerable 扩展方法。(如果你被困在 .NET 2.0 上,你可以复制 Mono 的)。如果它是一些无法获取项目但必须获取索引的自定义集合,则其他一些 Enumerable 方法(例如 Select)会为您传递整数索引。

于 2008-10-16T01:05:28.583 回答
2

这是一个很好的方法

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();
于 2009-09-13T01:04:03.757 回答
2

这是我的通用解决方案

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)删除项目时仍然有效。

于 2010-02-17T20:00:27.310 回答
0

根据您使用收藏的方式,您可以采取另一种方法。如果您一次下载作业(例如,当应用程序运行时),您可以即时将集合转换为哈希表,其中:

短名称 => SPRoleAssignment

如果您这样做,那么当您想通过短名称删除项目时,您需要做的就是按键从哈希表中删除该项目。

不幸的是,如果您大量加载这些 SPRoleAssignments,那么就时间而言,这显然不会更具成本效益。如果您使用的是 .NET Framework 的新版本,其他人提出的关于使用 Linq 的建议会很好,但否则,您必须坚持使用的方法。

于 2008-10-16T05:26:38.227 回答
0

这里有很多好的回应;我特别喜欢 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;
}
于 2008-10-16T14:14:23.297 回答
0

要在遍历集合时执行此操作而不是修改集合异常,这是我过去采用的方法(注意原始集合末尾的 .ToList() ,这会在内存中创建另一个集合,那么你可以修改现有的集合)

foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments.ToList())
{
    if (spAssignment.Member.Name == shortName)
    {
        workspace.RoleAssignments.Remove(spAssignment);
    }
}
于 2012-05-07T19:44:17.717 回答
0

类似于 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 客户端配置文件(3.5 和 4.5)中失败,一些查看者提到它在 .Net4.0 中对他们来说是失败的,并且不确定是哪些设置导致了问题。

因此,用下面的代码 (.ToList()) 替换 Where 语句,以避免该错误。“收藏已修改;枚举操作可能无法执行。”</p>

var itemsToDelete = sourceDict.Where(DictItem => DictItem.Value == false).ToList();

每 MSDN 从 .Net4.5 开始,客户端配置文件已停止使用。http://msdn.microsoft.com/en-us/library/cc656912(v=vs.110).aspx

于 2014-09-04T17:26:11.207 回答
0

首先保存您的项目,而不是删除它们。

var itemsToDelete = Items.Where(x => !!!your condition!!!).ToArray();
for (int i = 0; i < itemsToDelete.Length; ++i)
    Items.Remove(itemsToDelete[i]);

您需要GetHashCode()在您的 Item 类中覆盖。

于 2016-01-25T10:05:32.883 回答
0

最好的方法是使用 linq。

示例类:

 public class Product
    {
        public string Name { get; set; }
        public string Price { get; set; }      
    }

LINQ查询:

var subCollection = collection1.RemoveAll(w => collection2.Any(q => q.Name == w.Name));

collection1如果Name匹配任何元素Name,则此查询将删除所有元素collection2

记得使用:using System.Linq;

于 2018-04-23T11:52:59.140 回答
0

如果你有一个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

于 2020-07-26T07:18:04.633 回答