从集合中删除项目的最佳方法


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个其他变量。


78
喜欢变量名称。我不希望成为被删除的@ss。
tvanfosson

如果您打算这样做,请在成功查找时添加一个break语句,尽管如果始终使用成员名查找内容,使用Dictionary可能会更好。
tvanfosson

我认为您的意思是在代码段中使用RemoveAt(),因为您正在传递索引。一旦知道该项目,就可以直接调用Remove()。
赫斯特

这个问题应该澄清。答案涉及哪些.Net框架?我们是在谈论List <T>还是其他实现IList <T>的结构-应该将其重命名为“ .net 3.0中从List <T>中删除项目的最佳方法是什么?”
Sam Saffron

Answers:


28

如果要通过其属性之一访问集合的成员,则可以考虑使用Dictionary<T>KeyedCollection<T>代替。这样,您不必搜索所需的项目。

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

foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments)
{
    if (spAssignment.Member.Name == shortName)
    {
        workspace.RoleAssignments.Remove(spAssignment);
        break;
    }
}

13
这将导致异常,因为您在使用集合时正在对其进行修改...
RWendi,

14
不,不是。这是因为删除项目后会有休息。
jfs

25
如果某个人读了这句话并将其应用于一种情况,那就是删除多个项目,然后将多个索引保存到一个数组中,并使用一个单独的for循环,该循环在删除数组中向后循环以删除这些项目。
罗伯特·巴特

或者简单地使用workspace.RoleAssignments = workspace.RoleAssignments.Where(x => x.Member.Name!= shortName).ToList();进行过滤。但在可能的情况下,我更喜欢利亚姆的回答。
大卫

138

如果RoleAssignments是a List<T>,则可以使用以下代码。

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

我认为只有RemoveAll带有function参数。至少,我无法使用Remove构建它。
MichaelGG

5
是的,它是RemoveAll。我实际上花了一些时间检查,确认它是RemoveAll并仍粘贴在Remove中。太糟糕的stackoverflow没有内置的编译器:)
JaredPar

如果我没有记错的话,那会生成一个新列表。
理查德·尼纳伯

4
AFAIK是就地删除。
乔恩·林贾普

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]现在TedMary。我们最终会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);
}

2
对于简单的List <T>(如果您需要删除多个项目),相反的for循环总是最有效的方法。它肯定比将数据复制到listToRemove列表中更有效。我敢打赌,Linq实现使用相同的技巧。
山姆番红花

@ sambo99我完全同意并尝试扩大您的答案,解释为什么正向迭代不总是有效。我还指出,如果没有额外的信息,则最多删除1个条目时,反向迭代既没有效率,也没有效率更低。O(n)与列表一样好。
罗伯特·保尔森

是的 我现在正在纠正此问题... List <T>具有RemoveAt的非常讨厌的实现,最有效的方法似乎是将我们需要的数据复制出列表
Sam Saffron

假设您要从左端删除数据(例如,您想删除最左边的项目,但您不知道有多少个)。显然,反向解决方案是最糟糕的方法。...在您的示例中,而不是WHOOPSIE!您应该有书面清单。RemoveAt(idx--); 阿里格斯?

@ maxima120是正确的,但是如果您读了我说的话,我试图说明为什么逆向迭代在大多数情况下会更有效率。我并不是在说它的普遍适用性。如今,我不会打扰自己,而宁愿使用LINQ版本。
罗伯特·保尔森

11

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


如果要求从集合中删除一个以上的项目,则这是进行此操作的最佳方法。
Xeaz 2014年

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)这样的其他结构更适合于此目的。


7

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

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

(这在.NET 2.0中有效。当然,如果没有较新的编译器,则必须使用“ delegate(SPRoleAssignment spa){return 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中,则可以在其中复制Mono。)如果这是一个不能接受项目但必须接受索引的自定义集合,则其他一些Enumerable方法(例如Select)会为您传递整数索引。


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)删除项目,列表循环也仍然有效。


那不是可行的解决方案!您将跳过列表末尾和中间的项目!例如var result = string[]{"mike", "mike", "john", "ali", "mike"},尝试仅删除第一个“麦克”
flindeberg

@flindeberg我想知道您的意思是“跳过”,因为循环遍历了所有项目吗?...但是我将尝试您的数据
Jalal El-Shaer'5

在删除元素后增加索引(idx)时,您将跳过“下一个”元素。例如:如果您删除位置4的元素,则位置5将“移动”到位置4位置6- >位置5,(n)->(n-1)等,然后将索引增加到5,其中您会发现(旧6,新5),而您已跳过(旧5,新4)。这可以澄清事情吗?
flindeberg 2012年

很好,我更新了答案...感谢您注意到;)
Jalal El-Shaer 2012年

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();

0

您可以采用另一种方法,具体取决于您使用集合的方式。如果您一次下载任务(例如,应用运行时),则可以将集合即时转换为哈希表,其中:

短名称=> SPRoleAssignment

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

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


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语句,以避免该错误。“收藏被修改;枚举操作可能无法执行。”

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

从.Net4.5开始的每个MSDN,客户端配置文件均已终止。http://msdn.microsoft.com/zh-CN/library/cc656912(v=vs.110).aspx


0

首先保存您的项目,然后再删除它们。

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

您需要GetHashCode()在Item类中重写。


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匹配任何元素Namecollection2

请记住使用: using System.Linq;


0

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

foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments.ToList())
{
    if (spAssignment.Member.Name == shortName)
    {
        workspace.RoleAssignments.Remove(spAssignment);
    }
}

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


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;
}

2
我对使用FirstOrDefault的其他建议还是更好。Things.Remove(things.FirstOrDefault(t => t.Whatever == true))
MichaelGG,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.