RemoveAll for ObservableCollections?

45,357

Solution 1

I am not aware of a way to remove only the selected items. But creating an extension method is straight forward:

public static class ExtensionMethods
{
    public static int Remove<T>(
        this ObservableCollection<T> coll, Func<T, bool> condition)
    {
        var itemsToRemove = coll.Where(condition).ToList();

        foreach (var itemToRemove in itemsToRemove)
        {
            coll.Remove(itemToRemove);
        }

        return itemsToRemove.Count;
    }
}

This removes all items from the ObservableCollection that match the condition. You can call it like that:

var c = new ObservableCollection<SelectableItem>();
c.Remove(x => x.IsSelected);

Solution 2

Iterating backwards should be more efficient than creating a temporary collection as in Daniel Hilgarth's example.

public static class ObservableCollectionExtensions
{
    public static void RemoveAll<T>(this ObservableCollection<T> collection,
                                                       Func<T, bool> condition)
    {
        for (int i = collection.Count - 1; i >= 0; i--)
        {
            if (condition(collection[i]))
            {
                collection.RemoveAt(i);
            }
        }
    }
}

Solution 3

How about this implementation for a one-liner?

observableCollection.Where(l => l.type == invalid).ToList().All(i => observableCollection.Remove(i))

-- Edit --

Sorry, yes, you need a ToList() in the middle to force the first half to evaluate, as LINQ does lazy evaluation by default.

Solution 4

Each of solution proposed here which uses routine to remove item one by one has one fault. Imagine that you have many items in observable collection, lets say 10.000 items. Then you want to remove items which meets some condition.

If you use solution from Daniel Hilgarth and call: c.Remove(x => x.IsSelected); and there are for example 3000 items to be removed, proposed solution will notify about each item removal. This is due to fact that internal implementation of Remove(item) notify about that change. And this will be called for each of 3000 items in removal process.

So instead of this i created descendant of ObservableCollection and add new method RemoveAll(predicate)

[Serializable]
public class ObservableCollectionExt<T> : ObservableCollection<T>
{
    public void RemoveAll(Predicate<T> predicate)
    {
        CheckReentrancy();

        List<T> itemsToRemove = Items.Where(x => predicate(x)).ToList();
        itemsToRemove.ForEach(item => Items.Remove(item));

        OnPropertyChanged(new PropertyChangedEventArgs("Count"));
        OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
}

Interesting line is itemsToRemove.ForEach(item => Items.Remove(item));. Calling directly Items.Remove(item) will not notify about item removed.

Instead after removal of required items, changes are notified at once by calls:

OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));

Solution 5

This is my version of an extension method solution, which is only a slight variation on the accepted answer, but has the advantage that the count returned is based on confirmed removal of the item from the collection:

public static class ObservableCollectionExtensionMethods
{
    /// <summary>
    /// Extends ObservableCollection adding a RemoveAll method to remove elements based on a boolean condition function
    /// </summary>
    /// <typeparam name="T">The type contained by the collection</typeparam>
    /// <param name="observableCollection">The ObservableCollection</param>
    /// <param name="condition">A function that evaluates to true for elements that should be removed</param>
    /// <returns>The number of elements removed</returns>
    public static int RemoveAll<T>(this ObservableCollection<T> observableCollection, Func<T, bool> condition)
    {
        // Find all elements satisfying the condition, i.e. that will be removed
        var toRemove = observableCollection
            .Where(condition)
            .ToList();

        // Remove the elements from the original collection, using the Count method to iterate through the list, 
        // incrementing the count whenever there's a successful removal
        return toRemove.Count(observableCollection.Remove);
    }
}
Share:
45,357
Arpit Khandelwal
Author by

Arpit Khandelwal

Updated on July 05, 2022

Comments

  • Arpit Khandelwal
    Arpit Khandelwal about 2 years

    I am looking for Linq way (like RemoveAll method for List) which can remove selected items from my ObservableCollection.

    I am too new to create an extension method for myself. Is there any way I remove items from ObservableCollection passing a Lambda expression?

  • JoanComasFdz
    JoanComasFdz almost 12 years
    Will it be necessary to add itemsToRemove.Clear() before return statement?
  • Daniel Hilgarth
    Daniel Hilgarth almost 12 years
    @JoanComasFdz: No. itemstoRemove will be claimed by the garbage collector.
  • Daniel Hilgarth
    Daniel Hilgarth about 11 years
    @ShaktiPrakashSingh: Good points. Returning a collection indicates that the original one is unchanged and a new one has been created. As you point out, that is not the case. Furthermore, the method on List<T> returns the count, so it would be a good idea to conform to that. I changed my answer accordingly. Thanks for your comments.
  • cookbr
    cookbr about 9 years
    WPF Note: When using this with ItemsControl, the control will call Clear internally when using NotifyCollectionChangedAction.Reset; this resulted in undesired side effects in my specific use case when I had animations on newly added items ~ referencesource.microsoft.com/PresentationFramework/R/…
  • usefulBee
    usefulBee over 8 years
    i tried this one but ended up with error: "Collection was modified; enumeration operation may not execute."
  • danio
    danio over 8 years
    In a real world app under test, I found this was indeed faster than the temp collection - average time of 44ms and peak 62ms vs the temp collection with average time of 55ms and peak 93ms
  • danio
    danio over 8 years
    In a real world app under test, I found this was actually slower than the temp collection - average time of 67ms and peak 152ms vs the temp collection with average time of 55ms and peak 93ms. Bear in mind this was only for a small collection though.
  • Shimeon
    Shimeon about 8 years
    I found this to be a very simple/elegant solution. Are there any drawbacks to this over the longer solutions with more up votes?
  • simonalexander2005
    simonalexander2005 about 8 years
    readability, perhaps. It's not clear on first read what it's doing, whereas the accepted answer splits it into two more obvious parts.
  • Nicolas Bodin-Ripert
    Nicolas Bodin-Ripert almost 7 years
    @DanielHilgarth Thanks a lot, you made my day !
  • grek40
    grek40 over 6 years
    I'd recommend List<T>.ForEach instead of All. I think the All(...) is an artifact from before ToList() was added.
  • Farhana Naaz Ansari
    Farhana Naaz Ansari over 5 years
    please try to add some brief about your answer.
  • Daniel Hilgarth
    Daniel Hilgarth almost 4 years
    @Andreas: There is no such method on ObservableCollection
  • Andreas
    Andreas almost 4 years
    @DanielHilgarth Lol, it was an extension from inside the company code base. Deleted comment.