I have an MVC3 C#.Net web app. I have the below string array.
public static string[] HeaderNamesWbs = new[]
{
WBS_NUMBER,
BOE_TITLE,
SOW_DESCRIPTION,
HARRIS_WIN_THEME,
COST_BOGEY
};
I want to find the Index of a given entry when in another loop. I thought the list would have an IndexOf. I can't find it. Any ideas?
Well you can use Array.IndexOf:
int index = Array.IndexOf(HeaderNamesWbs, someValue);
Or just declare HeaderNamesWbs as an IList<string> instead - which can still be an array if you want:
public static IList<string> HeaderNamesWbs = new[] { ... };
Note that I'd discourage you from exposing an array as public static, even public static readonly. You should consider ReadOnlyCollection:
public static readonly ReadOnlyCollection<string> HeaderNamesWbs =
new List<string> { ... }.AsReadOnly();
If you ever want this for IEnumerable<T>, you could use:
var indexOf = collection.Select((value, index) => new { value, index })
.Where(pair => pair.value == targetValue)
.Select(pair => pair.index + 1)
.FirstOrDefault() - 1;
(The +1 and -1 are so that it will return -1 for "missing" rather than 0.)
I'm late to the thread here. But I wanted to share my solution to this. Jon's is awesome, but I prefer simple lambdas for everything.
You can extend LINQ itself to get what you want. It's fairly simple to do. This will allow you to use syntax like:
// Gets the index of the customer with the Id of 16.
var index = Customers.IndexOf(cust => cust.Id == 16);
This is likely not part of LINQ by default because it requires enumeration. It's not just another deferred selector/predicate.
Also, please note that this returns the first index only. If you want indexes (plural), you should return an IEnumerable<int> and yield return index inside the method. And of course don't return -1. That would be useful where you are not filtering by a primary key.
public static int IndexOf<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
var index = 0;
foreach (var item in source) {
if (predicate.Invoke(item)) {
return index;
}
index++;
}
return -1;
}
If you want to search List with a function rather than specifying an item value, you can use List.FindIndex(Predicate match).
See https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1.findindex?view=netframework-4.8
Right List has IndexOf(), just declare it as ILIst<string> rather than string[]
public static IList<string> HeaderNamesWbs = new List<string>
{
WBS_NUMBER,
BOE_TITLE,
SOW_DESCRIPTION,
HARRIS_WIN_THEME,
COST_BOGEY
};
int index = HeaderNamesWbs.IndexOf(WBS_NUMBER);
MSDN: List(Of T).IndexOf Method (T)
Related
I have a method that finds differences between two lists of ints using a dictionary. Essentially the code loops the first list, adding each int to the dictionary and setting (to 1 where not already present)/incrementing the value. It then loops the second list setting (to -1 where not already present)/decrementing the value.
Once it has looped both lists you end up with a dictionary where keys with values = 0 indicate a match, keys with values >=1 indicate presence only in the first list and values <=-1 indicate presence only in the second list.
Firstly, is this a sensible implementation?
Secondly, I would like to make it more generic, at the moment it can only handle int based lists. I'd like something that could handle any object where the caller could potentially define the comparison logic...
public static Dictionary<int, int> CompareLists(List<int> listA, List<int> listB)
{
// 0 Match
// <= -1 listB only
// >= 1 listA only
var recTable = new Dictionary<int, int>();
foreach (int value in listA)
{
if (recTable.ContainsKey(value))
recTable[value]++;
else
recTable[value] = 1;
}
foreach (int value in listB)
{
if (recTable.ContainsKey(value))
recTable[value]--;
else
recTable[value] = -1;
}
return recTable;
}
Thanks in advance!
In response to:
"It won't work properly if to example you have same value appears twice in listA and once in listB, result will be positive, which say "listA only" in your comments."
Let me clarify; if a value appears twice in listA it should also appear twice in listB - So if a value is in listA twice and once in listB, I don't care which one from listA it picks to match, as long as the one non-reconciling item is reported correctly.
Imagine the use-case where you are trying to reconcile lots of payment amounts between two files, it's entirely feasible to have repeating amounts but it doesn't really matter which of the duplicates are matched as long as the non-reconciling values are reported.
To answer your second question, here's how to make it more generic:
public static Dictionary<T, int> CompareLists<T>(IEnumerable<T> listA,
IEnumerable<T> listB, IEqualityComparer<T> comp)
{
var recTable = new Dictionary<T, int>(comp);
foreach (var value in listA)
{
if (recTable.ContainsKey(value))
recTable[value]++;
else
recTable[value] = 1;
}
foreach (var value in listB)
{
if (recTable.ContainsKey(value))
recTable[value]--;
else
recTable[value] = -1;
}
return recTable;
}
This is more generic because:
I pass in the type T instead of an int.
I use IEnumerables instead of Lists.
I pass in an IEqualityComparer and pass it to the Dictionary constructor which needs to use it.
I use var in the foreach loops instead of int. You can also use T.
You call this code like this:
static void Main()
{
int[] arr1 = { 1, 2, 3 };
int[] arr2 = { 3, 2, 1 };
var obj = CompareLists(arr1, arr2, EqualityComparer<int>.Default);
Console.ReadLine();
}
Here's an example of implementing IEqualityComparer. This treats all odd ints as equal and all even ints as equal:
public class MyEq : IEqualityComparer<int>
{
public bool Equals(int x, int y)
{
return (x % 2) == (y % 2);
}
public int GetHashCode(int obj)
{
return (obj % 2).GetHashCode();
}
}
FullOuterJoin as found here: LINQ - Full Outer Join
public static Dictionary<int, int> CompareLists(List<int> listA, List<int> listB)
{
return listA.FullOuterJoin(listB,
a=>a, // What to compare from ListA
b=>b, // What to compare from ListB
(a,b,key)=>
new {key=key,value=0}, // What to return if found in both
new {key=key,value=-1},// What to return if found only in A
new {key=key,value=1}) // What to return if found only in B
.ToDictionary(a=>a.key,a=>a.value); // Only because you want a dictionary
}
You can do this using Generics:
public static Dictionary<T, int> CompareLists<T>(List<T> listA, List<T> listB)
{
// 0 Match
// <= -1 listB only
// >= 1 listA only
var recTable = new Dictionary<T, int>();
foreach (T value in listA)
{
if (recTable.ContainsKey(value))
recTable[value]++;
else
recTable[value] = 1;
}
foreach (T value in listB)
{
if (recTable.ContainsKey(value))
recTable[value]--;
else
recTable[value] = -1;
}
return recTable;
}
These are my two cents:
public static Dictionary<T, int> CompareLists<T>(List<T> left, List<T> right, IEqualityComparer<T> comparer)
{
Dictionary<T, int> result = left.ToDictionary(l => l, l => right.Any(r => comparer.Equals(l, r)) ? 0 : -1);
foreach (T r in right.Where(t => result.Keys.All(k => !comparer.Equals(k, t))))
result[r] = 1;
return result;
}
The method takes Lists of any type T and an IEqualityComparer for that type T. It then at first generates a dictionary of those elements contained in the "left" List, thereby checking if they are also in the "right" List and setting the value accordingly.
The second step adds the elements that are only contained in the "right" List with value 1.
If this is a sensible implementation depends on what you are trying to achieve with it. I think it's a short but still readable one, relying on proper implementation of the LINQ methods. Though there might be faster possibilities one could think about if this is for really big lists or an very often called method.
I have a List of strings. Its being generated elsewhere but i will generate it below to help describe this simplified example
var list = new List<string>();
list.Add("Joe");
list.Add("");
list.Add("Bill");
list.Add("Bill");
list.Add("");
list.Add("Scott");
list.Add("Joe");
list.Add("");
list.Add("");
list = TrimList(list);
I would like a function that "trims" this list and by trim I want to remove all items at the end of the array that are blank strings (the final two in this case).
NOTE: I still want to keep the blank one that is the second item in the array (or any other one that is just not at the end) so I can't do a .Where(r=> String.isNullOrEmpty(r))
I would just write it without any LINQ, to be honest- after all, you're modifying a collection rather than just querying it:
void TrimList(List<string> list)
{
int lastNonEmpty = list.FindLastIndex(x => !string.IsNullOrEmpty(x));
int firstToRemove = lastNonEmpty + 1;
list.RemoveRange(firstToRemove, list.Count - firstToRemove);
}
If you actually want to create a new list, then the LINQ-based solutions are okay... although potentially somewhat inefficient (as Reverse has to buffer everything).
Take advantage of Reverse and SkipWhile.
list = list.Reverse().SkipWhile(s => String.IsNullOrEmpty(s)).Reverse().ToList();
List<T> (not the interface) has a FindLastIndex method. Therefore you can wrap that in a method:
static IList<string> TrimList(List<string> input) {
return input.Take(input.FindLastIndex(x => !string.IsNullOrEmpty(x)) + 1)
.ToList();
}
This produces a copy, whereas Jon's modifies the list.
The only solution I can think of is to code a loop that starts at the end of the list and searches for an element that is not an empty string. Don't know of any library functions that would help. Once you know the last good element, you know which ones to remove.
Be careful not to modify the collection while you are iterating over it. Tends to break the iterator.
I always like to come up with the most generic solution possible. Why restrict yourself with lists and strings? Let's make an algorithm for generic enumerable!
public static class EnumerableExtensions
{
public static IEnumerable<T> TrimEnd<T>(this IEnumerable<T> enumerable, Predicate<T> predicate)
{
if (predicate == null)
{
throw new ArgumentNullException("predicate");
}
var accumulator = new LinkedList<T>();
foreach (var item in enumerable)
{
if (predicate(item))
{
accumulator.AddLast(item);
}
else
{
foreach (var accumulated in accumulator)
{
yield return accumulated;
}
accumulator.Clear();
yield return item;
}
}
}
}
Use it like this:
var list = new[]
{
"Joe",
"",
"Bill",
"Bill",
"",
"Scott",
"Joe",
"",
""
};
foreach (var item in list.TrimEnd(string.IsNullOrEmpty))
{
Console.WriteLine(item);
}
what is fastest way to remove duplicate values from a list.
Assume List<long> longs = new List<long> { 1, 2, 3, 4, 3, 2, 5 }; So I am interesting in use lambda to remove duplicate and returned : {1, 2, 3, 4, 5}. What is your suggestion?
The easiest way to get a new list would be:
List<long> unique = longs.Distinct().ToList();
Is that good enough for you, or do you need to mutate the existing list? The latter is significantly more long-winded.
Note that Distinct() isn't guaranteed to preserve the original order, but in the current implementation it will - and that's the most natural implementation. See my Edulinq blog post about Distinct() for more information.
If you don't need it to be a List<long>, you could just keep it as:
IEnumerable<long> unique = longs.Distinct();
At this point it will go through the de-duping each time you iterate over unique though. Whether that's good or not will depend on your requirements.
You can use this extension method for enumerables containing more complex types:
IEnumerable<Foo> distinctList = sourceList.DistinctBy(x => x.FooName);
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector)
{
var knownKeys = new HashSet<TKey>();
return source.Where(element => knownKeys.Add(keySelector(element)));
}
There is Distinct() method. it should works.
List<long> longs = new List<long> { 1, 2, 3, 4, 3, 2, 5 };
var distinctList = longs.Distinct().ToList();
If you want to stick with the original List instead of creating a new one, you can something similar to what the Distinct() extension method does internally, i.e. use a HashSet to check for uniqueness:
HashSet<long> set = new HashSet<long>(longs.Count);
longs.RemoveAll(x => !set.Add(x));
The List class provides this convenient RemoveAll(predicate) method that drops all elements not satisfying the condition specified by the predicate. The predicate is a delegate taking a parameter of the list's element type and returning a bool value. The HashSet's Add() method returns true only if the set doesn't contain the item yet. Thus by removing any items from the list that can't be added to the set you effectively remove all duplicates.
List<long> distinctlongs = longs.Distinct().OrderBy(x => x).ToList();
A simple intuitive implementation
public static List<PointF> RemoveDuplicates(List<PointF> listPoints)
{
List<PointF> result = new List<PointF>();
for (int i = 0; i < listPoints.Count; i++)
{
if (!result.Contains(listPoints[i]))
result.Add(listPoints[i]);
}
return result;
}
In-place:
public static void DistinctValues<T>(List<T> list)
{
list.Sort();
int src = 0;
int dst = 0;
while (src < list.Count)
{
var val = list[src];
list[dst] = val;
++dst;
while (++src < list.Count && list[src].Equals(val)) ;
}
if (dst < list.Count)
{
list.RemoveRange(dst, list.Count - dst);
}
}
What is the fastest way to determine if one IEnumerable contains all the elements of another IEnumerable when comparing a field/property of each element in both collections?
public class Item
{
public string Value;
public Item(string value)
{
Value = value;
}
}
//example usage
Item[] List1 = {new Item("1"),new Item("a")};
Item[] List2 = {new Item("a"),new Item("b"),new Item("c"),new Item("1")};
bool Contains(IEnumerable<Item> list1, IEnumerable<Item>, list2)
{
var list1Values = list1.Select(item => item.Value);
var list2Values = list2.Select(item => item.Value);
return //are ALL of list1Values in list2Values?
}
Contains(List1,List2) // should return true
Contains(List2,List1) // should return false
There is no "fast way" to do this unless you track and maintain some state that determines whether all values in one collection are contained in another. If you only have IEnumerable<T> to work against, I would use Intersect.
var allOfList1IsInList2 = list1.Intersect(list2).Count() == list1.Count();
The performance of this should be very reasonable, since Intersect() will enumerate over each list just once. Also, the second call to Count() will be optimal if the underlying type is an ICollection<T> rather than just an IEnumerable<T>.
You could also use Except to remove from the first list all values that exist in the second list, and then check if all values have been removed:
var allOfList1IsInList2 = !list1.Except(list2).Any();
This method had the advantage of not requiring two calls to Count().
C# 3.5+
Using Enumerable.All<TSource> to determine if all List2 items are contained in List1:
bool hasAll = list2Uris.All(itm2 => list1Uris.Contains(itm2));
This will also work when list1 contains even more than all the items of list2.
Kent's answer is fine and short, but the solution that he provides always requires iteration over the whole first collection. Here is the source code:
public static IEnumerable<TSource> Intersect<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
if (first == null)
throw Error.ArgumentNull("first");
if (second == null)
throw Error.ArgumentNull("second");
return Enumerable.IntersectIterator<TSource>(first, second, comparer);
}
private static IEnumerable<TSource> IntersectIterator<TSource>(IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
Set<TSource> set = new Set<TSource>(comparer);
foreach (TSource source in second)
set.Add(source);
foreach (TSource source in first)
{
if (set.Remove(source))
yield return source;
}
}
That is not always required. So, here is my solution:
public static bool Contains<T>(this IEnumerable<T> source, IEnumerable<T> subset, IEqualityComparer<T> comparer)
{
var hashSet = new HashSet<T>(subset, comparer);
if (hashSet.Count == 0)
{
return true;
}
foreach (var item in source)
{
hashSet.Remove(item);
if (hashSet.Count == 0)
{
break;
}
}
return hashSet.Count == 0;
}
Actually, you should think about using ISet<T> (HashSet<T>). It contains all required set methods. IsSubsetOf in your case.
The solution marked as the answer would fail in the case of repetitions. If your IEnumerable only contains distinct values then it would pass.
The below answer is for 2 lists with repetitions:
int aCount = a.Distinct().Count();
int bCount = b.Distinct().Count();
return aCount == bCount &&
a.Intersect(b).Count() == aCount;
You should use HashSet instead of Array.
Example:
List1.SetEquals(List2); //returns true if the collections contains exactly same elements no matter the order they appear in the collection
Reference
The only HasSet limitation is that we can't get item by index like List nor get item by Key like Dictionaries. All you can do is enumerate them(for each, while, etc)
the Linq operator SequenceEqual would work also (but is sensitive to the enumerable's items being in the same order)
return list1Uris.SequenceEqual(list2Uris);
Another way is to convert your superset list to a HashSet and use the IsSuperSet method of HashSet.
bool Contains(IEnumerable<Item> list1, IEnumerable<Item>, list2)
{
var list1Values = list1.Select(item => item.Value);
var list2Values = list2.Select(item => item.Value).ToHashSet();
return list2Values.IsSupersetOf(list1Values);
}
What is the best way to approach removing items from a collection in C#, once the item is known, but not it's index. This is one way to do it, but it seems inelegant at best.
//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 would really like to do is find the item to remove by property (in this case, name) without looping through the entire collection and using 2 additional variables.
If RoleAssignments is a List<T> you can use the following code.
workSpace.RoleAssignments.RemoveAll(x =>x.Member.Name == shortName);
If you want to access members of the collection by one of their properties, you might consider using a Dictionary<T> or KeyedCollection<T> instead. This way you don't have to search for the item you're 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;
}
}
#smaclell asked why reverse iteration was more efficient in in a comment to #sambo99.
Sometimes it's more efficient. Consider you have a list of people, and you want to remove or filter all customers with a credit rating < 1000;
We have the following data
"Bob" 999
"Mary" 999
"Ted" 1000
If we were to iterate forward, we'd soon get into trouble
for( int idx = 0; idx < list.Count ; idx++ )
{
if( list[idx].Rating < 1000 )
{
list.RemoveAt(idx); // whoops!
}
}
At idx = 0 we remove Bob, which then shifts all remaining elements left. The next time through the loop idx = 1, but
list[1] is now Ted instead of Mary. We end up skipping Mary by mistake. We could use a while loop, and we could introduce more variables.
Or, we just reverse iterate:
for (int idx = list.Count-1; idx >= 0; idx--)
{
if (list[idx].Rating < 1000)
{
list.RemoveAt(idx);
}
}
All the indexes to the left of the removed item stay the same, so you don't skip any items.
The same principle applies if you're given a list of indexes to remove from an array. In order to keep things straight you need to sort the list and then remove the items from highest index to lowest.
Now you can just use Linq and declare what you're doing in a straightforward manner.
list.RemoveAll(o => o.Rating < 1000);
For this case of removing a single item, it's no more efficient iterating forwards or backwards. You could also use Linq for this.
int removeIndex = list.FindIndex(o => o.Name == "Ted");
if( removeIndex != -1 )
{
list.RemoveAt(removeIndex);
}
If it's an ICollection then you won't have a RemoveAll method. Here's an extension method that will do it:
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/
For a simple List structure the most efficient way seems to be using the Predicate RemoveAll implementation.
Eg.
workSpace.RoleAssignments.RemoveAll(x =>x.Member.Name == shortName);
The reasons are:
The Predicate/Linq RemoveAll method is implemented in List and has access to the internal array storing the actual data. It will shift the data and resize the internal array.
The RemoveAt method implementation is quite slow, and will copy the entire underlying array of data into a new array. This means reverse iteration is useless for List
If you are stuck implementing this in a the pre c# 3.0 era. You have 2 options.
The easily maintainable option. Copy all the matching items into a new list and and swap the underlying list.
Eg.
List<int> list2 = new List<int>() ;
foreach (int i in GetList())
{
if (!(i % 2 == 0))
{
list2.Add(i);
}
}
list2 = list2;
Or
The tricky slightly faster option, which involves shifting all the data in the list down when it does not match and then resizing the array.
If you are removing stuff really frequently from a list, perhaps another structure like a HashTable (.net 1.1) or a Dictionary (.net 2.0) or a HashSet (.net 3.5) are better suited for this purpose.
What type is the collection? If it's List, you can use the helpful "RemoveAll":
int cnt = workspace.RoleAssignments
.RemoveAll(spa => spa.Member.Name == shortName)
(This works in .NET 2.0. Of course, if you don't have the newer compiler, you'll have to use "delegate (SPRoleAssignment spa) { return spa.Member.Name == shortName; }" instead of the nice lambda syntax.)
Another approach if it's 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 the Enumerable extension methods. (You can copy the Mono ones in, if you are stuck on .NET 2.0). If it's some custom collection that cannot take an item, but MUST take an index, some of the other Enumerable methods, such as Select, pass in the integer index for you.
This is my generic solution
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 look much simpler if extension methods support passing by reference !
usage:
var result = string[]{"mike", "john", "ali"}
result = result.Remove(x => x.Username == "mike").ToArray();
Assert.IsTrue(result.Length == 2);
EDIT: ensured that the list looping remains valid even when deleting items by decrementing the index (idx).
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();
There is another approach you can take depending on how you're using your collection. If you're downloading the assignments one time (e.g., when the app runs), you could translate the collection on the fly into a hashtable where:
shortname => SPRoleAssignment
If you do this, then when you want to remove an item by short name, all you need to do is remove the item from the hashtable by key.
Unfortunately, if you're loading these SPRoleAssignments a lot, that obviously isn't going to be any more cost efficient in terms of time. The suggestions other people made about using Linq would be good if you're using a new version of the .NET Framework, but otherwise, you'll have to stick to the method you're using.
Similar to Dictionary Collection point of view, I have done this.
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:
Above code will fail in .Net Client Profile (3.5 and 4.5) also some viewers mentioned it is
Failing for them in .Net4.0 as well not sure which settings are causing the problem.
So replace with below code (.ToList()) for Where statement, to avoid that error. “Collection was modified; enumeration operation may not execute.”
var itemsToDelete = sourceDict.Where(DictItem => DictItem.Value == false).ToList();
Per MSDN From .Net4.5 onwards Client Profile are discontinued. http://msdn.microsoft.com/en-us/library/cc656912(v=vs.110).aspx
Save your items first, than 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 your Item class.
The best way to do it is by using linq.
Example class:
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 elements from collection1 if Name match any element Name from collection2
Remember to use: using System.Linq;
To do this while looping through the collection and not to get the modifying a collection exception, this is the approach I've taken 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);
}
}
If you have got a List<T>, then List<T>.RemoveAll is your best bet. There can't be anything more efficient. Internally it does the array moving in one shot, not to mention it is O(N).
If all you got is an IList<T> or an ICollection<T> you got roughly these three options:
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);
}
}
}
or
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);
}
}
or
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);
}
}
Go for either 1 or 2.
1 is lighter on memory and faster if you have less deletes to perform (i.e. predicate is false most of the times).
2 is faster if you have more deletes to perform.
3 is the cleanest code but performs poorly IMO. Again all that depends on input data.
For some benchmarking details see https://github.com/dotnet/BenchmarkDotNet/issues/1505
A lot of good responses here; I especially like the lambda expressions...very clean. I was remiss, however, in not specifying the type of Collection. This is a SPRoleAssignmentCollection (from MOSS) that only has Remove(int) and Remove(SPPrincipal), not the handy RemoveAll(). So, I have settled on this, unless there is a better suggestion.
foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments)
{
if (spAssignment.Member.Name != shortName) continue;
workspace.RoleAssignments.Remove((SPPrincipal)spAssignment.Member);
break;
}