Reputation: 5465
I'm having some trouble with the following code:
AllAgents.CollectionChanged += (sender, e) =>
{
if (e.Action != NotifyCollectionChangedAction.Remove) return;
foreach (var s in AllSnapshots)
{
foreach (var stat in s.Stats.Where(stat => stat.Model.Agent == null))
s.Stats.Remove(stat);
}
};
This of course, throws a "Collection was modified; enumeration operation may not execute." exception which makes total sense to me. The question is, what is the best way to accomplish removing all items where the Model.Agent property is null from my ObservableCollection? I'm open to other solutions as well, essentially, when an Agent is removed from the AllAgents collection, I need to remove any entries from my AllSnapshots.Stats collection that referred to that Agent.
Upvotes: 1
Views: 2414
Reputation: 21
There is a removeall method in the observablecollection now as I can see it in .net core 7 - just mentioning it in case anyone starts re-inventing the wheel by accident (I do it all the time too - just trying to be helpul).
Upvotes: 0
Reputation: 8818
If each Snapshot has a fairly low number of Stats, I'd probably do this:
foreach(var s in AllSnapShots)
{
s.Stats = new ObservableCollection<Stats>(s.Where(stat =>stat.Model.Agent != null));
}
So that will build a new collection with the elements that aren't null. If the list of stats is fairly big, though, you could also do this:
List<WhateverYourCollectionWasOf> statsToDelete;
foreach(var s in AllSnapShots)
{
statsToDelete.AddRange(s.Stats.Where(stat => stat.Model.Agent == null));
foreach (var stat in statsToDelete)
{
s.Stats.Remove(stat);
}
}
Or, if you wanted to be a little more future proof, you could write an extension method to do this sort of thing:
public static class ExtensionMethods
{
public static ObservableCollection<T> RemoveAll<T>(
this ObservableCollection<T> collection, Func<T, bool> predicate)
{
List<T> collectionAsList = collection.ToList();
List<T> itemsToRemove = collection.Where(predicate);
collectionAsList.RemoveAll(i => itemsToRemove.Contains(i));
collection = new ObservableCollection<T>(collectionAsList);
return collection;
}
}
Which would then be called like this:
foreach(var s in AllSnapShots)
{
s.Stats.RemoveAll(stat => stat.Model.Agent == null);
}
Upvotes: 0
Reputation: 7592
The best way to remove items from a collection like this would be to use a standard for
loop construct and iterate over the collection backwards. Since you are no longer relying on an enumerator to iterate over the collection, you can modify it safely without interfering with the loop.
Iterating in reverse prevents a removal from interfering with the indexes during the course of the loop, which would otherwise open you up to the possibility that some elements could end up not being removed.
ObservableCollection<Stat> o = new ObservableCollection<Stat>();
for (int i = o.Count - 1; i >= 0; i--) {
if (o[i] == null)
o.RemoveAt(i);
}
Upvotes: 5
Reputation: 15557
The problem is you're iterating and deleting and try to iterate again. This isn't supported by the ObservableCollection
cause it doesn't track which elements were deleted.
Replace your foreach
with an standard numerated for which doesn't depend on the Collection to be not modified and that's all.
This solution defers from Sam I am reply in that doesn't use additional memory to create another list (which can be pretty expensive if you´re element's count is big enough).
Upvotes: 1
Reputation: 31184
What i'd probably do is generate a list of stats to remove, and then remove them all in a seperate foreach loop
foreach (var s in AllSnapshots)
{
list<object> stats = new list<object>
//if this doesn't work i'll leave it up to you to look up how to list a generic type
foreach (var stat in s.Stats.Where(stat => stat.Model.Agent == null))
stats.add(stat);
foreach(var stat in stats)
s.Stats.Remove(stat);
}
Upvotes: 0