0

Is there a way to create a consuming foreach loop in C#? basically loop through a Collection items and simultaneously consume them?

In plain English: Instead of just looping through the elements, remove the item from the collection, do stuff with it, then go to the next.

EDIT I neglected to mention that I am using a producing consuming pattern and this is why I wanted a consuming foreach. Most of the answers here are completely valid for the simple case I described before this edit, although what I wanted is described in my answer

John Demetriou
  • 4,093
  • 6
  • 52
  • 88
  • 3
    Use a [`Stack`](https://msdn.microsoft.com/en-us/library/3278tedw(v=vs.110).aspx) or [`Queue`](https://msdn.microsoft.com/en-us/library/system.collections.queue(v=vs.110).aspx) – Tim Schmelter Nov 11 '15 at 12:39
  • What do you mean by "remove the item"? Do you mean you want to reduce the number of items in the collection? – rory.ap Nov 11 '15 at 12:39
  • 3
    You can look into the producer-consumer pattern, .NET provides the `BlockingCollection`. – Maarten Nov 11 '15 at 12:41
  • 1
    You can't modify a collection while you iterate through itlike that, you'll get a runtime error. – Ron Beyer Nov 11 '15 at 12:41
  • @roryap Yes but in a foreach loop – John Demetriou Nov 11 '15 at 12:42
  • You can not do that with an `IEnumerable` as it only exposes a way to iterate the collection. Unless the underlying class just happened to be implemented in such a way, but you wouldn't know that if you are consuming just `IEnuemerable`. – juharr Nov 11 '15 at 12:43
  • 1
    @RonBeyer has it right. You can't do that in a foreach loop. You can use a for loop – rory.ap Nov 11 '15 at 12:43
  • I would recommand that you have a look on RX and TPL DataFlow – Boas Enkler Nov 11 '15 at 12:55

5 Answers5

4

Use a Stack(last-in-first-out) or Queue(first-in, first-out).

A nice example is this "recursive" queue which lists all the files in a directory.

Community
  • 1
  • 1
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
2

The collection used in foreach is immutable. This is made by design.

MSDN says:

The foreach statement is used to iterate through the collection to get the information that you want, but can not be used to add or remove items from the source collection to avoid unpredictable side effects. If you need to add or remove items from the source collection, use a for loop.

Update 0.0: It is possible to use for loop to edit collection: To add an item to collection, you can use for loop:

List<string> coll = new List<string>() { "1", "2", "3", "4", "5" };
for (int i = coll.Count-1; i>=0 ; i--)
{ 
   /* You can assign value to an item:
    coll[i] = "";       
   You can add an item to the collection:
   coll.Add(i.ToString());*/
   coll.RemoveAt(i);
   //or coll.Remove("your string value to delete");
}
StepUp
  • 36,391
  • 15
  • 88
  • 148
  • So show a way that it *can* be done, i.e. using a for loop. As it stands, this should be a comment not an answer. – rory.ap Nov 11 '15 at 12:44
  • 1
    You cannot remove from a list in a `for` loop that increments up because each time you remove something the `Count` will go down. You either need to start at the end and decrement or use a `while` loop like Konamiman's answer – juharr Nov 11 '15 at 12:55
  • Downvoted because using an incremental loop will either end up to `IndexOutOfBounds` Exception or lead to items being skipped – John Demetriou Nov 11 '15 at 13:59
  • @StepUp removed the downvote and upvoted as it is valid for the simple case that I described in the question, although information was missing from my question – John Demetriou Nov 11 '15 at 14:33
  • 1
    @StepUp You need to loop until `i >= 0` or you'll miss the first one at index 0. – juharr Nov 11 '15 at 14:50
2

As pointed out in the comments for your question, using a Stack or a Queue is the best option for a "process-then-remove" pattern. If for some reason you dont't want to use these specialized classes, you can do something like the following with a plain List:

var items = new List<Item>() {item1, item2, item3};
while(items.Count > 0)
{
    ProcessItem(item[0]);
    items.RemoveAt(0);
}

But what you cannot do is to modify the collection inside the foreach loop, as explained in StepUp's answer.

Konamiman
  • 49,681
  • 17
  • 108
  • 138
1

As stated in the answer from StepUp the collection used in the foreach loop is immutable. But a similar thing could be performed with BlockingCollection and using GetConsumingEnumerable which consumes the Collection items while iterating through them. Given that my collection is part of concurrent produce consume scheme (that I neglected to mention in my question) this suits me more. In simpler cases though the other answers, using a stack or queue are much more appropriate, or simple loop that would loop from the end of the collection, down to the start and remove the item on the way (a start to end loop with removal will skip items or end up in an IndexOutOfBounds Exception

John Demetriou
  • 4,093
  • 6
  • 52
  • 88
1

Not exactly a "consuming foreach" but probably what you are looking for is the TPL DataFlow.

See for example:https://leanpub.com/tpldataflowbyexample/read or MSDN: Create a DataFlow Pipeline or MSDN: Implement a Producer / Consumer Scenario

A very very basic example can look like this:

  var actionBlock = new ActionBlock<int>(n => Console.WriteLine(n));

             for (int i = 0; i < 10; i++) {
                 actionBlock.Post(i);
             }

             Console.WriteLine("Done");

You can also link multiple blocks to create a more complex consuming strategy with forks and so on. You can also defined varying Scheduler types to the steps.

For very easy scenarios this might be oversized, but TPL is a very good scaling solution with give you a lot of control on how the dataflow should look like.

Boas Enkler
  • 12,264
  • 16
  • 69
  • 143