ChrisO
ChrisO

Reputation: 5465

Deleting null items from an ObservableCollection

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

Answers (5)

Chase
Chase

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

Phillip Schmidt
Phillip Schmidt

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

mclark1129
mclark1129

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

Erre Efe
Erre Efe

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

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

Related Questions