The best way to remove items from a collection

What is the best way to approach removing items from a collection in C # once the item is known but not an index. This is one way to do this, but at best it seems inelegant.

//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 really would like to do is find the element to delete by property (in this case, the name), without looping the entire collection and using 2 additional variables.

+66
collections c #
Oct. 16 '08 at 0:43
source share
14 answers

If you want to access collection members for one of your properties, you can use Dictionary<T> or KeyedCollection<T> instead. This way you do not need to search for the item you are looking for.

Otherwise, you could at least do this:

 foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments) { if (spAssignment.Member.Name == shortName) { workspace.RoleAssignments.Remove(spAssignment); break; } } 
+25
Oct 16 '08 at 0:50
source share

If RoleAssignments is a List<T> , you can use the following code.

 workSpace.RoleAssignments.RemoveAll(x =>x.Member.Name == shortName); 
+127
Oct 16 '08 at 1:04
source share

@smaclell asked why reverse iteration was more efficient in commenting on @ sambo99.

This is sometimes more effective. You have a list of people and you want to remove or filter all customers with a credit rating of <1000;

The following data are available

 "Bob" 999 "Mary" 999 "Ted" 1000 

If we iterated ahead, we would soon be in trouble

 for( int idx = 0; idx < list.Count ; idx++ ) { if( list[idx].Rating < 1000 ) { list.RemoveAt(idx); // whoops! } } 

At idx = 0, delete Bob , which then shifts all remaining elements to the left. Next time through the loop idx = 1, but list [1] is now Ted instead of Mary . As a result, we skip Mary by mistake. We could use a while loop, and we could introduce more variables.

Or we just go to iteration:

 for (int idx = list.Count-1; idx >= 0; idx--) { if (list[idx].Rating < 1000) { list.RemoveAt(idx); } } 

All indexes to the left of the deleted item remain unchanged, so you do not skip any items.

The same principle applies if you are given a list of indexes to remove from the array. For everything to be in order, you need to sort the list and then remove the items from the highest index to the lowest.

Now you can simply use Linq and declare what you are doing, in a straightforward manner.

 list.RemoveAll(o => o.Rating < 1000); 



In this case, it is more efficient to remove one element, iterating forward or backward. You can also use Linq for this.

 int removeIndex = list.FindIndex(o => o.Name == "Ted"); if( removeIndex != -1 ) { list.RemoveAt(removeIndex); } 
+21
Oct. 16 '08 at 4:33
source share

For a simple list structure, the most efficient way is to use the Predicate RemoveAll implementation.

Eg.

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

Causes:

  • The Predicate / Linq RemoveAll method is implemented in the List and has access to the internal array storing the actual data. It will shift the data and resize the internal array.
  • The implementation of the RemoveAt method is rather slow and will copy the entire base data array into a new array. That means reverse iteration is useless for List

If you are stuck in implementing this in the pre C # 3.0 era. You have 2 options.

  • Easily maintained option. Copy all the relevant items into the new list and replace the base list.

Eg.

 List<int> list2 = new List<int>() ; foreach (int i in GetList()) { if (!(i % 2 == 0)) { list2.Add(i); } } list2 = list2; 

Or

  • A sophisticated, faster option that involves moving all the data in the list down when they do not match, and then resizing the array.

If you rarely remove material from the list, perhaps a different structure, such as HashTable (.net 1.1) or Dictionary (.net 2.0) or HashSet (.net 3.5) is better suited for this purpose.

+9
Oct. 16 '08 at 1:12
source share

If this is an ICollection then you will not have a RemoveAll method. Here is an extension method that will do this:

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

Based on: http://phejndorf.wordpress.com/2011/03/09/a-removeall-extension-for-the-collection-class/

+9
Apr 17 '13 at 11:16
source share

What type of collection? If this is a list, you can use the useful "RemoveAll":

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

(This works in .NET 2.0. Of course, if you do not have a new compiler, you will have to use "delegate (SPRoleAssignment spa) {return spa.Member.Name == shortName;}" instead of the pretty lambda syntax.)

Another approach, if it is not a list, but still an ICollection:

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

This requires Enumerable extension methods. (You can copy Mono if you are stuck on .NET 2.0). If there is some kind of custom collection that cannot take an element but MUST accept an index, some of the other Enumerable methods, such as Select, pass you an integer index.

+7
Oct 16 '08 at 1:05
source share

Here is a pretty good way to do it.

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(); 
+2
Sep 13 '09 at 1:04
source share

This is my common decision.

 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 be much easier if extension methods supported link passing! Using:

 var result = string[]{"mike", "john", "ali"} result = result.Remove(x => x.Username == "mike").ToArray(); Assert.IsTrue(result.Length == 2); 

EDIT: Make sure the list loop remains valid even when deleting items by decreasing the index (idx).

+2
Feb 17 '10 at 20:00
source share

There is another approach that you can use depending on how you use your collection. If you download tasks once (for example, when the application starts), you can transfer the collection on the fly to a hash table, where:

shortname => SPRoleAssignment

If you do this, then when you want to remove the item by short name, all you need to do is remove the item from the hash table using the key.

Unfortunately, if you download these SPRoleAssignments a lot, this obviously will not be more economical in terms of time. Suggestions made by other users about using Linq would be nice if you are using the new version of the .NET Framework, but otherwise you will have to stick with the method you are using.

0
Oct. 16 '08 at 5:26
source share

There are many good reviews here; I especially like lambda expressions ... very clean. However, I did not agree not to specify the type of collection. This is an SPRoleAssignmentCollection (from MOSS), which only has Remove (int) and Remove (SPPrincipal), and not convenient RemoveAll (). So, I settled on this if there is no better offer.

 foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments) { if (spAssignment.Member.Name != shortName) continue; workspace.RoleAssignments.Remove((SPPrincipal)spAssignment.Member); break; } 
0
Oct. 16 '08 at 14:14
source share

To do this during the loop through the collection, and not to get a collection change exception, I used this approach 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)

 foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments.ToList()) { if (spAssignment.Member.Name == shortName) { workspace.RoleAssignments.Remove(spAssignment); } } 
0
May 7 '12 at 19:44
source share

As in the dictionary collection, I did it.

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

Note: .Net Client Profile (3.5 and 4.5) will not succeed above the code, also some viewers mentioned that Otherwise, in .Net4.0 they are also not sure which settings are causing the problem.

Therefore, replace the code below (.ToList ()) for the Where statement to avoid this error. "The collection has been modified; an enumeration operation cannot be performed."

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

In MSDN From .Net4.5 onwards, the client profile is terminated. http://msdn.microsoft.com/en-us/library/cc656912(v=vs.110).aspx

0
Sep 04 '14 at 17:26
source share

Save your items first and then delete them.

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

You need to override GetHashCode() in the Item class.

0
Jan 25 '16 at 10:05
source share

The best way to do this is to use linq.

Class Example:

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

Linq query:

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

This query will remove all items from collection1 if Name matches any Name item from collection2

Remember to use: using System.Linq;

0
Apr 23 '18 at 11:52
source share



All Articles