12

I can see why this is not allowed:

foreach (Thing t in myCollection) {
   if (shouldDelete(t) {
      myCollection.Delete(t);
   }
}

but how about this?

foreach (Thing t in myCollection.Where(o=>shouldDelete(o)) {
   myCollection.Delete(t);
}

I don't understand why this fails. The "Where()" method obviously isn't returning the original collection, so I am not enumerating round the original collection when I try to remove something from it.

svick
  • 236,525
  • 50
  • 385
  • 514
Andy
  • 10,412
  • 13
  • 70
  • 95
  • There is something i dont get about this "Thing" thing. Its certainly not a built in type thing is it? somebody enlighten me.... – Deb Apr 19 '12 at 01:54
  • Sorry if the question was confusing. Thing could be any class at all and myCollecion is any ICollection. FWIW Eric has completely understood and answered my original question so it's closed as far as I'm concerned. – Andy Apr 19 '12 at 07:35

6 Answers6

26

I don't understand why this fails.

I assume your question then is "why does this fail?" (You forgot to actually ask a question in your question.)

The "Where()" method obviously isn't returning the original collection

Correct. "Where" returns an IEnumerable<T> which represents the collection with a filter put on top of it.

so I am not enumerating round the original collection when I try to remove something from it.

Incorrect. You are enumerating the original collection. You're enumerating the original collection with a filter put on top of it.

When you call "Where" it does not eagerly evaluate the filter and produce a brand new copy of the original collection with the filter applied to it. Rather, it gives you an object which enumerates the original collection, but skips over the items that do not match the filter.

When you're at a store and you say "show me everything", the guy showing you everything shows you everything. When you say "now just show me the apples that are between $1 and $5 a kilogram", you are not constructing an entirely new store that has only apples in it. You're looking at exactly the same collection of stuff as before, just with a filter on it.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
13

Try use this code

myCollection.RemoveAll(x => x.shouldDelete(x));
Likurg
  • 2,742
  • 17
  • 22
9

You can do:

myCollection.RemoveAll(shouldDelete);
Scroog1
  • 3,539
  • 21
  • 26
  • Thanks, I actually wanted to do something else inside the loop so Likurg's suggestion was better for me, but I have +1'd you :-) – Andy Apr 16 '12 at 14:24
  • 2
    ... if the type of `myCollection` has a RemoveAll method! `List` does, but many collection types do not. – phoog Apr 16 '12 at 22:02
6

The second statement returns a IEnumerable<> operating on your list. This one should be okay:

foreach (Thing t in myCollection.Where(o=>shouldDelete(o).ToList()) {
   myCollection.Delete(t);
}
Matten
  • 17,365
  • 2
  • 42
  • 64
  • thanks, this is the solution I have implemented, but I marked Eric as the answer because what I really wanted was to know what was happening underneath – Andy Apr 16 '12 at 14:26
2

It is because collection should not be modified withing the foreach loop. It attempts to delete it before entire foreach loop is executed. Hence it will fail.

Marshal
  • 6,551
  • 13
  • 55
  • 91
1

Where extension method filter the collection values based on the passed predicate and returns IEnumerable. Hence can't modify the collection while iteration.

You can use RemoveAll() for your purpose.

Cinchoo
  • 6,088
  • 2
  • 19
  • 34