Remove repetitive, hard coded loops and conditions in C# - c#

I have a class that compares 2 instances of the same objects, and generates a list of their differences. This is done by looping through the key collections and filling a set of other collections with a list of what has changed (this may make more sense after viewing the code below). This works, and generates an object that lets me know what exactly has been added and removed between the "old" object and the "new" one.
My question/concern is this...it is really ugly, with tons of loops and conditions. Is there a better way to store/approach this, without having to rely so heavily on endless groups of hard-coded conditions?
public void DiffSteps()
{
try
{
//Confirm that there are 2 populated objects to compare
if (NewStep.Id != Guid.Empty && SavedStep.Id != Guid.Empty)
{
//<TODO> Find a good way to compare quickly if the objects are exactly the same...hash?
//Compare the StepDoc collections:
OldDocs = SavedStep.StepDocs;
NewDocs = NewStep.StepDocs;
Collection<StepDoc> docstoDelete = new Collection<StepDoc>();
foreach (StepDoc oldDoc in OldDocs)
{
bool delete = false;
foreach (StepDoc newDoc in NewDocs)
{
if (newDoc.DocId == oldDoc.DocId)
{
delete = true;
}
}
if (delete)
docstoDelete.Add(oldDoc);
}
foreach (StepDoc doc in docstoDelete)
{
OldDocs.Remove(doc);
NewDocs.Remove(doc);
}
//Same loop(s) for StepUsers...omitted for brevity
//This is a collection of users to delete; it is the collection
//of users that has not changed. So, this collection also needs to be checked
//to see if the permisssions (or any other future properties) have changed.
foreach (StepUser user in userstoDelete)
{
//Compare the two
StepUser oldUser = null;
StepUser newUser = null;
foreach(StepUser oldie in OldUsers)
{
if (user.UserId == oldie.UserId)
oldUser = oldie;
}
foreach (StepUser newie in NewUsers)
{
if (user.UserId == newie.UserId)
newUser = newie;
}
if(oldUser != null && newUser != null)
{
if (oldUser.Role != newUser.Role)
UpdatedRoles.Add(newUser.Name, newUser.Role);
}
OldUsers.Remove(user);
NewUsers.Remove(user);
}
}
}
catch(Exception ex)
{
string errorMessage =
String.Format("Error generating diff between Step objects {0} and {1}", NewStep.Id, SavedStep.Id);
log.Error(errorMessage,ex);
throw;
}
}
The targeted framework is 3.5.

Are you using .NET 3.5? I'm sure LINQ to Objects would make a lot of this much simpler.
Another thing to think about is that if you've got a lot of code with a common pattern, where just a few things change (e.g. "which property am I comparing?" then that's a good candidate for a generic method taking a delegate to represent that difference.
EDIT: Okay, now we know we can use LINQ:
Step 1: Reduce nesting
Firstly I'd take out one level of nesting. Instead of:
if (NewStep.Id != Guid.Empty && SavedStep.Id != Guid.Empty)
{
// Body
}
I'd do:
if (NewStep.Id != Guid.Empty && SavedStep.Id != Guid.Empty)
{
return;
}
// Body
Early returns like that can make code much more readable.
Step 2: Finding docs to delete
This would be much nicer if you could simply specify a key function to Enumerable.Intersect. You can specify an equality comparer, but building one of those is a pain, even with a utility library. Ah well.
var oldDocIds = OldDocs.Select(doc => doc.DocId);
var newDocIds = NewDocs.Select(doc => doc.DocId);
var deletedIds = oldDocIds.Intersect(newDocIds).ToDictionary(x => x);
var deletedDocs = oldDocIds.Where(doc => deletedIds.Contains(doc.DocId));
Step 3: Removing the docs
Either use the existing foreach loop, or change the properties. If your properties are actually of type List<T> then you could use RemoveAll.
Step 4: Updating and removing users
foreach (StepUser deleted in usersToDelete)
{
// Should use SingleOfDefault here if there should only be one
// matching entry in each of NewUsers/OldUsers. The
// code below matches your existing loop.
StepUser oldUser = OldUsers.LastOrDefault(u => u.UserId == deleted.UserId);
StepUser newUser = NewUsers.LastOrDefault(u => u.UserId == deleted.UserId);
// Existing code here using oldUser and newUser
}
One option to simplify things even further would be to implement an IEqualityComparer using UserId (and one for docs with DocId).

As you are using at least .NET 2.0 I recommend implement Equals and GetHashCode ( http://msdn.microsoft.com/en-us/library/7h9bszxx.aspx ) on StepDoc. As a hint to how it can clean up your code you could have something like this:
Collection<StepDoc> docstoDelete = new Collection<StepDoc>();
foreach (StepDoc oldDoc in OldDocs)
{
bool delete = false;
foreach (StepDoc newDoc in NewDocs)
{
if (newDoc.DocId == oldDoc.DocId)
{
delete = true;
}
}
if (delete) docstoDelete.Add(oldDoc);
}
foreach (StepDoc doc in docstoDelete)
{
OldDocs.Remove(doc);
NewDocs.Remove(doc);
}
with this:
oldDocs.FindAll(newDocs.Contains).ForEach(delegate(StepDoc doc) {
oldDocs.Remove(doc);
newDocs.Remove(doc);
});
This assumes oldDocs is a List of StepDoc.

If both StepDocs and StepUsers implement IComparable<T>, and they are stored in collections that implement IList<T>, then you can use the following helper method to simplify this function. Just call it twice, once with StepDocs, and once with StepUsers. Use the beforeRemoveCallback to implement the special logic used to do your role updates. I'm assuming the collections don't contain duplicates. I've left out argument checks.
public delegate void BeforeRemoveMatchCallback<T>(T item1, T item2);
public static void RemoveMatches<T>(
IList<T> list1, IList<T> list2,
BeforeRemoveMatchCallback<T> beforeRemoveCallback)
where T : IComparable<T>
{
// looping backwards lets us safely modify the collection "in flight"
// without requiring a temporary collection (as required by a foreach
// solution)
for(int i = list1.Count - 1; i >= 0; i--)
{
for(int j = list2.Count - 1; j >= 0; j--)
{
if(list1[i].CompareTo(list2[j]) == 0)
{
// do any cleanup stuff in this function, like your role assignments
if(beforeRemoveCallback != null)
beforeRemoveCallback(list[i], list[j]);
list1.RemoveAt(i);
list2.RemoveAt(j);
break;
}
}
}
}
Here is a sample beforeRemoveCallback for your updates code:
BeforeRemoveMatchCallback<StepUsers> callback =
delegate(StepUsers oldUser, StepUsers newUser)
{
if(oldUser.Role != newUser.Role)
UpdatedRoles.Add(newUser.Name, newUser.Role);
};

What framework are you targeting? (This will make a difference in the answer.)
Why is this a void function?
Shouldn't the signature look like:
DiffResults results = object.CompareTo(object2);

If you want to hide the traversal of the tree-like structure you could create an IEnumerator subclass that hides the "ugly" looping constructs and then use CompareTo interface:
MyTraverser t =new Traverser(oldDocs, newDocs);
foreach (object oldOne in t)
{
if (oldOne.CompareTo(t.CurrentNewOne) != 0)
{
// use RTTI to figure out what to do with the object
}
}
However, I'm not at all sure that this particularly simplifies anything. I don't mind seeing the nested traversal structures. The code is nested, but not complex or particularly difficult to understand.

Using multiple lists in foreach is easy. Do this:
foreach (TextBox t in col)
{
foreach (TextBox d in des) // here des and col are list having textboxes
{
// here remove first element then and break it
RemoveAt(0);
break;
}
}
It works similar as it is foreach (TextBox t in col && TextBox d in des)

Related

Reassign object in foreach loop c#

Not sure I understand why I can do this with a for loop and not a foreach loop?
This is the code that works. Looping through a BindingList Products, finding a match and then assigning that product at index i to the new product that's passed in.
public static void UpdateProduct(int productToUpdateID, Product productToUpdate)
{
for (int i = 0; i < Products.Count; i++)
{
if (Products[i].ProductID == productToUpdateID)
{
Products[i] = productToUpdate;
}
}
}
If I try to do this with a foreach loop I get an error that I cannot assign to the iterator variable. What is the reasoning for this and is there a way to get around it or is using a for loop for this kind of problem the best solution?
This is essentially what I'm trying to do.
public static void UpdateProduct(int productToUpdateID, Product productToUpdate)
{
foreach(Product product in Products)
{
if (product.ProductID == productToUpdateID)
{
product = productToUpdate;
}
}
}
I can do something like this and reassign all the properties explicitly but want to see if there is another way to do it.
foreach(Product product in Products)
{
if (product.ProductID == productToUpdateID)
{
product.Name = productToUpdate.Name;
}
}
Thanks!
The foreach construct is for when you want to do something with each item in the list. That does not seem to be what you are doing. You are modifying the list itself, by removing an item and replacing it.
Personally I would not use a loop at all, I'd just remove the old item and add the new one.
public static void UpdateProduct(int productToUpdateID, Product productToUpdate)
{
Products.RemoveAll( x => x.ProductID == productToUpdateID );
Products.Add( productToUpdate );
}
Or if you wish to preserve order:
public static void UpdateProduct(int productToUpdateID, Product productToUpdate)
{
var index = Products.FindIndex( x => x.ProductID == productToUpdateID );
Products[index] = productToUpdate;
}
The reasons have already been given, but as a minor detail: this is sometimes possible; there is an alternative syntax in recent C# that uses a ref-local for the iterator value:
foreach (ref [readonly] SomeType value in source)
which is only available for some scenarios - naked arrays, spans, or custom iterator types with a ref-return Current - and as long as the optional readonly modifier is not used, you can assign directly via the value variable, since this is a direct reference to the underlying source. The uses for this are rare and niche. If Products is a List<T>, you could combine this with CollectionMarshal.AsSpan(...) to achieve what you want, but frankly I'd consider that hacky (apart from other things, it would bypass the list's internal change protections). Basically: don't do this, but : it isn't entirely impossible.
The foreach loop iterates over the elements of a collection, and the iteration variable is simply a reference to the current element in the collection.
The reason you cannot modify the iteration variable itself is that it is a read-only reference to the element in the collection. Modifying the iteration variable would not change the element in the collection; it would only change the reference.
Alternative ways are already mentioned in the above answers.
Just for the record. IMHO the best way is to use a foreach loop with a modified code like this. It only makes one iteration
int i=-1;
foreach (var product in products )
{
i++;
if (product.ProductID == productToUpdate.ProductID)
{
products[i]=productToUpdate;
break;
}
}
But if you want to use linq for some reason, you can do it in one line
products = products.Select(x => x = x.ProductID == productToUpdate.ProductID?productToUpdate:x).ToList();

Can I rewrite this do-while loop as a foreach loop?

Is there a way to write this code more elegantly with a foreach loop? The "create a new entry" logic is thwarting me, because it needs to execute even if pendingEntries contains no items.
ItemDto itemToAdd; // an input parameter to the method
IEnumerator<Item> pendingEntries = existingPendingItems.GetEnumerator();
pendingEntries.MoveNext();
do // foreach entry
{
Item entry = pendingEntries.Current;
if (entry != null) // fold the itemToAdd into the existing entry
{
entry.Quantity += itemToAdd.Quantity; // amongst other things
}
else // create a new entry
{
entry = Mapper.Map<ItemDto, Item>(itemToAdd);
}
Save(entry);
} while (pendingEntries.MoveNext());
This should be rewritten. I don't know what kind of collection you're using, but Current is undefined in your case since MoveNext could have returned false. As stated in the documentation:
Current is undefined under any of the following conditions:
The last call to MoveNext returned false, which indicates the end of the
collection.
Here is how I would rewrite it:
bool isEmpty = true;
foreach (Item entry in existingPendingItems)
{
ProcessEntry(entry, itemToAdd);
isEmpty = false;
}
if (isEmpty)
{
ProcessEntry(null, itemToAdd);
}
ProcessEntry contains the logic for a single entry, and is easily unit testable.
The algorithm is cleared to read.
The enumerable is still only enumerated once.
foreach (Item entry in existingPendingItems.DefaultIfEmpty())
{
Item entryToSave;
if (entry != null) // fold the itemToAdd into the existing entry
{
entry.Quantity += itemToAdd.Quantity; // amongst other things
entryToSave = entry;
}
else // create a new entry
{
entryToSave = Mapper.Map<ItemDto, Item>(itemToAdd);
}
Save(entryToSave);
}
The key is the Enumerable.DefaultIfEmpty() call — this will return a sequence with a default (Item) item if the sequence is empty. This will be null for a reference type.
Edit: fixed bug mentioned by neotapir.
Personally I'd suggest something like this:
ItemDto itemToAdd; // an input parameter to the method
if (existingPendingItems.Any())
{
foreach(Item entry in existingPendingItems)
{
entry.Quantity += itemToAdd.Quantity
Save(entry);
}
}
else
{
entry = Mapper.Map<ItemDto, Item>(itemToAdd);
Save(entry);
}
I think this makes the intent of the code much clearer.
EDIT: Changed count to any as per suggestion. Also fixed the add quantity logic
I'd rewrite it as more standard while method. And you've forgot that IEnumerator<T> implements IDisposable, so you should dispose it.
foreach( Item entry in pendingEntries.Current)
{
if( entry != null)
entry.Quantity += itemToAdd.Quantity;
else
entry = Mapper.Map<ItemDto, Item>(itemToAdd);
Save(entry)
}
cant exactly test it without the items
var pendingEntries = existingPendingItems.Any()
? existingPendingItems
: new List<Item> { Mapper.Map<ItemDto, Item>(itemToAdd) };
foreach (var entry in pendingEntries)
{
entry.Quantity += itemToAdd.Quantity; // amongst other things
Save(entry);
}
The idea here is that you set yourself up for success before iterating. What are you going to iterate over? Either the existing entries, if there are any, or just a new entry otherwise.
By handling this up front, so you know you've got something with which to work, your loop stays very clean.

Is there a way to know I am getting the last element in the foreach loop

I need to do special treatment for the last element in the collection. I am wondering if I can know I hit the last element when using foreach loop.
Only way I know of is to increment a counter and compare with length on exit, or when breaking out of loop set a boolean flag, loopExitedEarly.
There isn't a direct way. You'll have to keep buffering the next element.
IEnumerable<Foo> foos = ...
Foo prevFoo = default(Foo);
bool elementSeen = false;
foreach (Foo foo in foos)
{
if (elementSeen) // If prevFoo is not the last item...
ProcessNormalItem(prevFoo);
elementSeen = true;
prevFoo = foo;
}
if (elementSeen) // Required because foos might be empty.
ProcessLastItem(prevFoo);
Alternatively, you could use the underlying enumerator to do the same thing:
using (var erator = foos.GetEnumerator())
{
if (!erator.MoveNext())
return;
Foo current = erator.Current;
while (erator.MoveNext())
{
ProcessNormalItem(current);
current = erator.Current;
}
ProcessLastItem(current);
}
It's a lot easier when working with collections that reveal how many elements they have (typically the Count property from ICollection or ICollection<T>) - you can maintain a counter (alternatively, if the collection exposes an indexer, you could use a for-loop instead):
int numItemsSeen = 0;
foreach(Foo foo in foos)
{
if(++numItemsSeen == foos.Count)
ProcessLastItem(foo)
else ProcessNormalItem(foo);
}
If you can use MoreLinq, it's easy:
foreach (var entry in foos.AsSmartEnumerable())
{
if(entry.IsLast)
ProcessLastItem(entry.Value)
else ProcessNormalItem(entry.Value);
}
If efficiency isn't a concern, you could do:
Foo[] fooArray = foos.ToArray();
foreach(Foo foo in fooArray.Take(fooArray.Length - 1))
ProcessNormalItem(foo);
ProcessLastItem(fooArray.Last());
Unfortunately not, I would write it with a for loop like:
string[] names = { "John", "Mary", "Stephanie", "David" };
int iLast = names.Length - 1;
for (int i = 0; i <= iLast; i++) {
Debug.Write(names[i]);
Debug.Write(i < iLast ? ", " : Environment.NewLine);
}
And yes, I know about String.Join :).
I see others already posted similar ideas while I was typing mine, but I'll post it anyway:
void Enumerate<T>(IEnumerable<T> items, Action<T, bool> action) {
IEnumerator<T> enumerator = items.GetEnumerator();
if (!enumerator.MoveNext()) return;
bool foundNext;
do {
T item = enumerator.Current;
foundNext = enumerator.MoveNext();
action(item, !foundNext);
}
while (foundNext);
}
...
string[] names = { "John", "Mary", "Stephanie", "David" };
Enumerate(names, (name, isLast) => {
Debug.Write(name);
Debug.Write(!isLast ? ", " : Environment.NewLine);
})
Not without jumping through flaming hoops (see above). But you can just use the enumerator directly (slightly awkward because of C#'s enumerator design):
IEnumerator<string> it = foo.GetEnumerator();
for (bool hasNext = it.MoveNext(); hasNext; ) {
string element = it.Current;
hasNext = it.MoveNext();
if (hasNext) { // normal processing
Console.Out.WriteLine(element);
} else { // special case processing for last element
Console.Out.WriteLine("Last but not least, " + element);
}
}
Notes on the other approaches I see here: Mitch's approach requires having access to a container which exposes it's size. J.D.'s approach requires writing a method in advance, then doing your processing via a closure. Ani's approach spreads loop management all over the place. John K's approach involves creating numerous additional objects, or (second method) only allows additional post processing of the last element, rather than special case processing.
I don't understand why people don't use the Enumerator directly in a normal loop, as I've shown here. K.I.S.S.
This is cleaner with Java iterators, because their interface uses hasNext rather than MoveNext. You could easily write an extension method for IEnumerable that gave you Java-style iterators, but that's overkill unless you write this kind of loop a lot.
Is it Special treatment can be done only while processing on the foreach loop, Is it you can't do that while adding to the collection. If this is your case, have your own custom collection,
public class ListCollection : List<string>
{
string _lastitem;
public void Add(string item)
{
//TODO: Do special treatment on the new Item, new item should be last one.
//Not applicable for filter/sort
base.Add(item);
}
}
List<int> numbers = new ....;
int last = numbers.Last();
Stack<int> stack = new ...;
stack.Peek();
update
var numbers = new int[] { 1, 2,3,4,5 };
var enumerator = numbers.GetEnumerator();
object last = null;
bool hasElement = true;
do
{
hasElement = enumerator.MoveNext();
if (hasElement)
{
last = enumerator.Current;
Console.WriteLine(enumerator.Current);
}
else
Console.WriteLine("Last = {0}", last);
} while (hasElement);
Console.ReadKey();
Deferred Execution trick
Build a class that encapsulates the values to be processed and the processing function for deferred execution purpose. We will end up using one instance of it for each element processed in the loop.
// functor class
class Runner {
string ArgString {get;set;}
object ArgContext {get;set;}
// CTOR: encapsulate args and a context to run them in
public Runner(string str, object context) {
ArgString = str;
ArgContext = context;
}
// This is the item processor logic.
public void Process() {
// process ArgString normally in ArgContext
}
}
Use your functor in the foreach loop to effect deferred execution by one element:
// intended to track previous item in the loop
var recent = default(Runner); // see Runner class above
// normal foreach iteration
foreach(var str in listStrings) {
// is deferred because this executes recent item instead of current item
if (recent != null)
recent.Process(); // run recent processing (from previous iteration)
// store the current item for next iteration
recent = new Runner(str, context);
}
// now the final item remains unprocessed - you have a choice
if (want_to_process_normally)
recent.Process(); // just like the others
else
do_something_else_with(recent.ArgString, recent.ArgContext);
This functor approach uses memory more but prevents you from having to count the elements in advance. In some scenarios you might achieve a kind of efficiency.
OR
Shorter Workaround
If you want to apply special processing to the last element after processing them all in a regular way ....
// example using strings
var recentStr = default(string);
foreach(var str in listStrings) {
recentStr = str;
// process str normally
}
// now apply additional special processing to recentStr (last)
It's a potential workaround.

IQueryable remove from the collection, best way?

IQueryable<SomeType> collection = GetCollection();
foreach (var c in collection)
{
//do some complex checking that can't be embedded in a query
//based on results from prev line we want to discard the 'c' object
}
//here I only want the results of collection - the discarded objects
So with that simple code what is the best way to get the results. Should I created a List just before the foreach and insert the objects I want to keep, or is there some other way that would be better to do this type of thing.
I know there are other posts on similar topics but I just don't feel I'm getting what I need out of them.
Edit I tried this
var collection = GetCollection().Where(s =>
{
if (s.property == 1)
{
int num= Number(s);
double avg = Avg(s.x);
if (num > avg)
return true;
else
return false;
}
else return false;
});
I tried this but was given "A lambda expression with a statement body cannot be converted to an expression tree" on compile. Did I not do something right?
//do some complex checking that can't be embedded in a query
I don't get it. You can pass a delegate which can point to a very complex function (Turing-complete) that checks whether you should discard it or not:
var result = GetCollection().AsEnumerable().Where(c => {
// ...
// process "c"
// return true if you want it in the collection
});
If you want, you can refactor it in another function:
var result = GetCollection.Where(FunctionThatChecksToDiscardOrNot);
If you wrap it into another method, you can use yield return and then iterate over the returned collection, like so:
public IEnumerable<SomeType> FindResults(IQueryable<SomeType> collection) {
foreach (var c in collection)
{
if (doComplicatedQuery(c)) {
yield return c;
}
}
}
// elsewhere
foreach (var goodItem in FindResults(GetCollection())) {
// do stuff.
}

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

Categories

Resources